REBOL

REBOL Crash Course

By: Nick Antonaccio
See http://re-bol.com for a complete tutorial.
Go to http://rebolforum.com to ask questions.

Contents:

1. Downloading, Installing, and Running REBOL
2. REBOL LANGUAGE BASICS:
2.1 Fundamentals
2.2 GUIs (Program Windows)
2.3 Blocks, Series, and Strings
2.4 Conditions
2.5 Loops
2.6 User Defined Functions and Imported Code
2.7 Quick Review and Synopsis
3. More Essential Topics
3.1 Built-In Help and Online Resources
3.2 Saving and Running REBOL Scripts
3.3 "Compiling" REBOL Programs - Distributing Packaged .EXE Files
3.4 Embedding Binary Resources and Using REBOL's Built In Compression
3.5 Running Command Line Applications
3.6 Responding to Special Events in a GUI - "Feel"
3.7 Common REBOL Errors, and How to Fix Them
4. EXAMPLE PROGRAMS - Learning How All The Pieces Fit Together
4.1 Little Email Client
4.2 Simple Web Page Editor
4.3 Card File
4.4 Little Menu Example
4.5 Loops and Conditions - A Simple Data Storage App
4.6 FTP Chat Room
4.7 Image Effector
4.8 Guitar Chord Diagram Maker
4.9 Shoot-Em-Up Video Game
4.10 Catch Game
4.11 Blogger
4.12 Listview Multi Column Data Grid Example
4.13 Thumbnail Maker
5. Additional Topics
5.1 Objects
5.2 Ports
5.3 Parse (REBOL's Answer to Regular Expressions)
5.4 2D Drawing, Graphics, and Animation
5.5 3D Graphics with r3D
5.6 Multitasking
5.7 Using DLLs and Shared Code Files in REBOL
5.8 Web Programming and the CGI Interface
5.9 WAP - Cell Phone Browser CGI Apps
5.10 REBOL as a Browser Plugin
5.11 Using Databases
5.12 Menus
5.13 Multi Column GUI Text Lists (Data Grids)
5.14 RebGUI
5.15 Creating PDF files using pdf-maker.r
5.16 Creating .swf Files with REBOL/Flash
5.17 Rebcode
5.18 Useful REBOL Tools
5.19 6 REBOL Flavors
5.20 Bindology, Dialects, Metaprogramming and Other Advanced Topics
6. A Generalized Approach to Writing Software:
7. REAL WORLD EXAMPLES
7.1 Case 1 - Online Schedule Text Editor
7.2 Case 2 - A Simple Image Gallery CGI Program
7.3 Case 3 - Days Between Two Dates Calculator
7.4 Case 4 - Simple Search
7.5 Case 5 - A Simple Calculator Application
7.6 Case 6 - A Backup Music Generator (Chord Accompaniment Player)
7.7 Case 7 - FTP Tool
7.8 Case 8 - Jeopardy Trainer
7.9 Case 9 - Tetris Game Clone
7.10 Case 10 - Scheduling Teachers, Part Two
7.11 Case 11 - An Online Member Page CGI Program
7.12 Case 12 - A CGI Event Calendar
7.13 Case 13 - Ski Game, Snake Game, and Space Invaders Shootup
7.14 Case 14 - Media Player (Wave/Mp3 Jukebox)
7.15 Case 15 - Creating the REBOL "Demo"
7.16 Case 16 - Guitar Chord Chart Printer
7.17 Case 17 - Web Site Content Management System (CMS), Sitebuilder.cgi
7.18 Case 18 - A GUI Playing Card Framework (Creating a Freecell Clone)
7.19 Case 19 - Downloading Directories - A Server Spidering App
7.20 Case 20 - Vegetable Gardening
7.21 Case 21 - An Additional Teacher Automation Project
8. Other Scripts
9. Learning More About REBOL - IMPORTANT DOCUMENTATION LINKS

1. Downloading, Installing, and Running REBOL

Download REBOL/View for your operating system at:

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

Installation is NOT required. Just run the downloaded executable, click the "Console" icon, and start typing code. Enter this code as an example:

alert "Hello world!"

To reopen REBOL's graphic desktop, type "desktop". To avoid having to click the Console icon every time REBOL starts, uncheck "Open Desktop On Startup" from the desktop USER menu.

The REBOL interpreter is about 1/2 meg to download (tiny) and takes approximately 5 seconds to install. If you install REBOL, any text file with a ".r" extension can be clicked and run just like an .exe program. You can also use REBOL's built-in text editor to edit code. Type "editor none" into the REBOL console, and press the F5 key to automatically run any code in the editor.

2. REBOL LANGUAGE BASICS:

2.1 Fundamentals

REBOL "functions" are words that perform actions. Functions are typically followed by data "parameters" ("arguments"). Try pasting the following function examples into the REBOL console:

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 only produce "return" values:

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

The "rejoin" function returns contatenated (joined together) text strings:

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

Some functions have "refinements" which change the way they perform:

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."

REBOL code is evaluated strictly left to right, and then downward line by line. No delimiters are required between functions:

alert "First function" alert "Second function"

You can use the output (return) value from any function, as the input (data parameter) for any other function:

alert rejoin ["Hello " "there" "!"] ; alert the returned concatenated text
editor request-text  ; edit the returned text (entered by the user)

Parentheses can be used to clarify order of evaluation (math evaluations are always performed strictly left-to-right):

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

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

White space and indentation can be used as desired:

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

Comments are preceded by semicolons:

; this is a comment

Multiline comments can be created with curly braces:

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

The COLON symbol is used to assign word labels ("variables") to values:

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

Word labels are NOT case sensitive:

alert person
alert PERSON
alert PeRsOn

Word labels can be assigned to any value returned by any function:

filename: request-file
alert rejoin ["You chose " filename]

The "ask" function is a simple way to get text input at the command line:

name: ask "What is your name? "

The "print" function displays text output at the command line:

print rejoin ["Good to meet you " name]

The "prin" function prints without line breaks:

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 is followed by a carriage return." newline]
print "This text is 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  ;  "%" is used to represent local file names

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:

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:

editor http://rebol.com
editor pop://user:pass@website.com
editor clipboard://
print read dns://msn.com
print read nntp://public.teranews.com

The editor reads, AND allows you to SAVE EDITS back to a 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:

write clipboard:// (read http://rebol.com)   ; try pasting afterward
write ftp://user:pass@website2.com (read http://website1.com)

Sending email is just as easy:

send user@website.com "Hello"
send user@website.com (read %file.txt)

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

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:

picture: load http://rebol.com/view/bay.jpg
save/png %/c/picture.png picture

"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.). "read" and "write" are used to store and retrieve typical types of data, exactly byte for byte without conversion or formatting.

REBOL can automatically 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"     type? some-text
an-integer: 3874904                        type? an-integer
a-decimal: 7348.39                         type? a-decimal    
web-site: http://musiclessonz.com          type? web-site 
email-address: user@website.com            type? email-address      
the-file: %/c/myfile.txt                   type? the-file 
bill-amount: $343.56                       type? bill-amount
html-tag: <br>                             type? html-tag    
binary-info:  #{ddeedd}                    type? binary-info    
image: load http://rebol.com/view/bay.jpg  type? image
a-sound: load %/c/windows/media/tada.wav   a-sound/type

Data types can be "cast" to different types using "to-(type)" functions:

numbr: 4729                 ; the integer value 4729
strng: to-string numbr      ; the text characters "4729"

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

to-binary request-color       ; hex value
to-tuple #{00CD00}            ; RGB value

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 (commonly used to 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)   ; easier then the routine above

2.2 GUIs (Program Windows)

The functions "view" and "layout" are used together to display GUI windows. Widgets are enclosed in brackets:

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
]

Adjust the visual characteristics of any widget 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

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"
]

To have widgets perform functions when clicked or otherwise activated, put the functions inside a set of brackets after the widget.

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 active 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, put the actions inside a second set of brackets after the widget:

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

To assign keyboard shortcuts to a widget, use the pound symbol:

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"
    ]
]

To assign a word label to any widget, use a colon. To refer to properties of a widget, use the "/" symbol. The "text" property is especially useful:

view layout [
    f: field "Type something here"
    btn "Display" [alert f/text]
]

This is an important concept. Here's another example:

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

And another:

view layout [
    page-to-read: field "http://rebol.com"
    btn "Display HTML" [
        editor (to-url page-to-read/text)
    ]
]

To SET the properties of any widget, use a colon. Use the "show" function to update the visual display:

view layout [
    a: area 600x350 
    btn "Load" [
        a/text: read %reboltut.txt
        show a
    ]
]

Here's another example:

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)  ; set the text
        show the-html
    ]
]

Below are two more examples of how colons are used to get AND set text values contained in widgets:

view layout [
    f: field
    btn "Display Variable" [
        t: f/text
        alert t    
    ]
]

view layout [
    f1: field
    btn "Display Variable" [
        t: f1/text
        f2/text: t
        show f2    
    ]
    f2: field
]

Here's a little text editor application that builds on the ideas above:

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"
    ]
]

The "offset" of a widget holds its coordinate position. Use it to move widgets:

view layout [
    size 600x440
    jumper: button "click me" [
        jumper/offset: random 580x420
    ]
]

The "style" function allows you to assign all the properties and actions of a widget, to a word label. 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
]

To display photos and other graphics in a GUI, use the "image" word (the "load" function downloads an image to be displayed):

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

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]]
view layout [image load http://rebol.com/view/bay.jpg effect [Grayscale]]

You can impose images onto most types of widgets (notice the "fit" effect):

view layout [area load http://rebol.com/view/bay.jpg]
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 apply colors directly to images. 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) 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 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 (screen shot):

picture: to-image layout [
    page-to-read: field "http://rebol.com"
    btn "Display HTML"
]
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 (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]

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]

Here's a little puzzle game:

view center-face layout [         ; center the window
    origin 0x0 space 0x0 across   ; layout parameters
    style piece button 60x60 [
        if not find [0x60 60x0 0x-60 -60x0] (face/offset - empty/offset) [
            exit
        ]
        temp: face/offset
        face/offset: empty/offset 
        empty/offset: temp
    ]
    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"
    empty: piece 200.200.200 edge [size: 0]
]

2.3 Blocks, Series, and Strings

In REBOL, all multiple pieces of grouped data items are stored in "blocks". Blocks are delineated by starting and ending square 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!"]]

Like any other variable data, blocks can be assigned word labels:

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

Blocks of text data 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 which match a certain criteria:

remove-each name some-names [find name "i"]  ; remove items containing "i"

Use "copy" to create an empty blocks:

empty-block: copy []   ; DO THIS to create an empty block
empty-block: []        ; DO _NOT_ DO THIS to create an empty block

Here's a typical example that uses a block to save text entered into the fields of a GUI:

view gui: layout [
    field1: field 
    field2: field 
    field3: field
    btn "Save" [
        save-block: copy []
        append save-block field1/text 
        append save-block field2/text 
        append save-block field3/text
        save %save.txt save-block
        alert {SAVED -- Now try running this script again, and load
            the data back into the fields.}
    ]
    btn "Load" [
        save-block: load %save.txt
        field1/text: save-block/1
        field2/text: save-block/2
        field3/text: save-block/3
        show gui  ; update the display
    ]
]

2.3.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

2.3.2 REBOL Strings

In REBOL, a "string" is simply a series of characters. Here are 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

Using series functions, you can often 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

Run the following script for an introduction to some more string functions:

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

2.3.3 Indentation

Blocks often contain other blocks. Starting and ending brackets are normally placed at the same indentation level, to group 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

2.3.4 More About Why/How Blocks are Useful

In REBOL, blocks can contain mixed data of ANY type:

some-items: ["item1" "item2" "item3" "item4"]
an-image: load http://rebol.com/view/bay.jpg
append some-items an-image
save/all %some-items.txt some-items
some-items: load %some-items.txt
view layout [image fifth some-items]

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.

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

You can refer to an item in a block by its index (position number):

view layout [image some-items/5]

You can obtain the index number of the last item in a block by determining the number of items in the block:

last-item: length? some-items

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

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

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

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

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")
alert pick some-items (index-num - 1)

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)]
view layout [image some-items/5]  ; same

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]

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

print pick some-items index-num
print compose [some-items/(index-num)]
print reduce [some-items/(index-num)]  
print some-items/:index-num

Here's an another example demonstrating the "compose" function:

photo1: load http://rebol.com/view/bay.jpg
photo2: load http://rebol.com/view/demos/palms.jpg
photo-block: compose [(photo1) (photo2)]
foreach photo photo-block [view layout [image photo]]

2.4 Conditions

2.4.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] ; parens not required

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

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

2.4.2 Either

"Either" does the first block if the condition is true, or the second block if the condition is false:

either (condition) [do this if true] [do this if false]

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!"
]

2.4.3 Switch

The "switch" evaluation chooses between multiple evaluations:

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]

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!"]

2.4.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!}]
]

2.4.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:

value1: value2: value3: true
value4: value5: value6: false
either ( (value1 = true) and (value2 = true) ) [
    print "both true"
] [
    print "not both true"
]

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

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

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

either ( (value1 = true) or (value4 = true) ) [
    print "either one OR the other is true"
] [
    print "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":

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

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

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

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

2.5 Loops

2.5.1 Forever

The "forever" function creates a endless repeating loop:

forever [block of actions to repeat]

The "break" function can be 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 another example:

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
    ]
]

And a little clock:

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

2.5.2 Loop

The "loop" function allows you to specify a number of times to repeat:

loop 50 [print "REBOL is great!"]

2.5.3 Repeat

The "repeat" function 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]
]

2.5.4 For

"For" loops allow you to specify a start value, end value, incremental value, and a variable to hold the current value during the loop:

for {word to hold current val} {start val} {end val} {increment val} [
    block of code to perform, which can use the current word val
]

For example:

for counter 1 10 1 [print counter] 
for counter 10 1 -1 [print counter] 
for counter 10 100 10 [print counter] 
for counter 1 5 .5 [print counter] 
for timer 8:00 9:00 0:05 [print timer] 
for dimes $0.00 $1.00 $0.10 [print dimes] 
for date 1-dec-2005 25-jan-2006 8 [print date]
for alphabet #"a" #"z" 1 [prin alphabet] 

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

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

2.5.5 Foreach

The "foreach" function lets you easily loop through a block of data:

foreach {word representing consecutive items in a given block} [
    given block
] [
    block of functions executed upon each item in the given block,
    using the word defined above to represent each successive item
]

For example:

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

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

some-names: ["John" "Bill" "Tom" "Mike"]
count: 0
foreach name some-names [
    count: count + 1
    print rejoin ["Item " count ": " name]
]

some-names: ["John" "Bill" "Tom" "Mike"]
data-block: copy []  ; remember to use "copy" to create empty blocks!
count: 0
foreach name some-names [
    count: count + 1
    append data-block rejoin ["Item " count ": " name newline]
]
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"
]
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
    ]
]

2.5.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]

2.5.7 While and Until

The "while" function repeatedly evaluates a block of code while the given condition is true:

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."
]

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 do everything in a given block, 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 a for loop to increment times values, 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."]
    ]
]

2.5.8 Loops in a GUI

The following code template can be used to accomplish forever loops in a GUI. The events in this loop will run SIMULTANEOUSLY along with other events 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
    ]]]
]

2.6 User Defined Functions and Imported Code

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:

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 placed 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"
]

; 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:

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

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: func [num1 num2] [print square-root (num1 + num2)]

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 by using the "/local" refinement (local variables are not changed throughout the rest of your program):

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

2.6.1 Importing Code

You can "do" a module of code contained in any text file, as long as it contains the minimum header "REBOL [ ]". 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

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

2.7 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.

3. More Essential Topics

3.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

Here's a script that saves REBOL's built-in function documentation to a text 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 show a list of words for automatic 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 help examples:

? 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:

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

3.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. Use path notation to explore it:

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

Info about REBOL's GUI components is in "system/view/VID":

? system/view/VID

Use this short cut to refer to system/view/VID:

? svv

A list of REBOL's native GUI widgets is in "svv/vid-styles":

editor svv/vid-styles

This script neatly displays all the words in "svv/vid-styles":

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 additional facets, type the path to the widget's "words" block, i.e.:

svv/vid-styles/TEXT-LIST/words

It's important to note that you can use a colon to SET _any_ system value:

system/user/email: user@website.com

3.1.2 Viewtop Resources

To see hundreds of interesting online "rebsite" code examples, type "desktop" into the REBOL interpreter, then click the "REBOL" or "Public" folders. Source code for every rebsite example program is available by right-clicking individual icons and selecting "edit". For cross-referenced information about built-in functions, see the REBOL Dictionary rebsite, found in the REBOL->Tools folder (an HTML version is also available at www.rebol.com/docs/dictionary.html).

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

If you can't find answers to REBOL 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. You can also ask questions of numerous users in AltME (http://www.rebol.org/aga-index.r), a messaging program which makes up the most active forum of REBOL users around the world. www.rebol.org maintains a searchable history of several hundred thousand posts from both the mailing list and AltME, along with a rich script archive. Unlike other programming communities, REBOL does not have a popular web based support forum. AltME is the primary way that REBOL developers interact.

3.2 Saving and Running REBOL Scripts

Whenever you save a REBOL program to a text file, the code must begin with the following bit of header text:

REBOL []

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 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 named "webcam.r" on your C:\ drive.

REBOL [title: "Webcam Viewer"]

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. With REBOL installed, .r scripts can be clicked and run as if they're executable programs
  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.
  3. Type "do %/c/webcam.r" into the REBOL interpreter.
  4. REBOL scripts can be run at the OS 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).
  5. Use a program such as XpackerX (http://www.marmaladefoo.com) or any other SFX program to package and distribute the program as an executable zip file
  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.

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

You can use "XpackerX" from http://www.marmaladefoo.com to produce a Windows .exe programs. To use XpackerX, first create a text file in the following format, and 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>

Download the free XpackerX program, then load and edit the above template so that it contains the filename(s) you've given to your script(s) and file(s), and the correct path to your REBOL interpreter. Executables created in this way will run just like any compiled .exe file (installing REBOL is NOT required to run this type of stand-alone program).

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".

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

Use following program to compress and encode external files (images, sounds, DLLs, .exe files, etc.) so that they can be included within the text of your program code:

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]

3.5 Running Command Line Applications

The "call" function executes commands in your computer's operating system (i.e., DOS and Unix commands).

call/show "notepad.exe c:\YOURNAME.txt"
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

Type "help call" to see the many options that allow you to monitor, control, and make use of output from external command line applications.

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

For GUIs to respond to events other than a mouse click directly on a widget, use the "feel" object and the "insert-event-func" function:

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

"f a e" can be used to shorten "face action event":

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

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 (an invisible box the same size as the screen, with a feel event attached, is used for full screen detection):

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"]
]

Timer events can be assigned to any widget:

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."]
        ]
    ]
]

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

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 [
        engage: func [f a e] [
            if a = 'down [initial-position: e/offset]
            if find [over away] a [
                f/offset: f/offset + (e/offset - initial-position)
            ]
            show f
        ]
    ]
] 600X440

You can add the following "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
    at random 600x400 moveable-object (random 255.255.255)  ; random color
    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:

view center-face layout [
    size 600x440
    at 270x209 b: btn "Click Me!" feel [
        detect: func [f e] [
            if e/type = 'move [
                if (within? e/offset b/offset 59x22) [
                    b/offset: b/offset + ((random 50x50) - (random 50x50))
                    if not within? b/offset -59x-22 659x462 [
                        b/offset: 270x209
                    ]
                    show b
                ]
            ]
            e ; ALWAYS return the detected event
        ]
    ]
]

To handle global events in a GUI such as resizing and closing, use "insert-event-func":

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 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
    ][event]
]

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

To remove an installed event handler, use "remove-event-func":

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."]

3.7 Common REBOL Errors, and How to Fix Them

1) "** Syntax Error: Script is missing a REBOL header" - minimum header is required on all saved files. Include the following text at the beginning of the script:

REBOL []

2) "** Syntax Error: Missing ] at end-of-script" - unclosed block bracket, parentheses, or string (indentation helps eliminate these errors):

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

; should be:

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

3) "** Script Error: request expected str argument of type: string block object none" - wrong type of value passed to a function:

website: http://rebol.com  ; the "alert" function requires a string value
alert website              ; error (the word 'website is now a URL value)

website: to-string http://rebol.com  ; convert the value to a string first
alert website                        ; no error

4) "** Script Error: word has no value" - usually caused by mis-spellings:

wrod: "Hello world"
print word           ; error - different spelling

5) To break out of an endless loop or any other errant code, just hit the [Esc] key on your keyboard. If an error occurs in a "view layout" block, type "unview" to close the broken GUI. To restart a stopped GUI, type "do-events".

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." - your mail server info needs to be set up in REBOL's user settings. Use the "set-net" function, or set individual values as follows:

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) Not using "copy" when creating empty blocks and strings can create undesired behavior:

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

; should be:

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

8) Load/Save, Read/Write, Mold, Reform, etc. errors - "Save" is used to convert and store data in common formats (images and other binary data types, blocks of data, etc). "Write" saves data in raw form. "Load" and "read" share a comparable relationship. "Load" reads data in a way that is automatically converted. "Read" opens data in exactly the format it's saved, byte for byte. Generally, data that is "save"d should be "load"ed, and data that's "write"ed should be "read". For more information, see the following links:

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 , http://www.rebol.net/cookbook/recipes/0015.html

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

2 + 4 * 6        ; This is the same as
(2 + 4) * 6      ; this.  The left side is evaluated first.
                 ; (6 * 6  == 36)

2 + (4 * 6)      ; NOT this  (2 + 24  == 26)

10) To fix interactivity problems when copying/pasting code into the interpreter (i.e., when the code contains functions such as "ask", which require a response from the user), 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, 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 trick when copying/pasting large scripts, is to run the code from the clipboard using "do read clipboard://"

3.7.1 Trapping Errors

The words "attempt", and "error?"/"try" together provide a way to check for and handle error situations:

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

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

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

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

Line-by-line explanations of each of these programs are available at http://re-bol.com .

4.1 Little Email Client

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
    ]
]

4.2 Simple Web Page Editor

REBOL [Title: "Web Page Editor"]

view layout [
    page-to-read: field 600 "ftp://user:pass@website.com/path/page.html"
    the-html: area 600x440
    across
    btn "Download HTML Page" [
        the-html/text: read (to-url page-to-read/text)
        show the-html
    ]
    btn "Load Local HTML File" [
        the-html/text: read (to-file request-file)
        show the-html
    ]
    btn "Save Changes to Web Site" [
        write (to-url page-to-read/text) the-html/text
    ]
    btn "Save Changes to Local File" [
        write (to-file request-file/save) the-html/text
    ]
]

4.3 Card File

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
    ]
]

4.4 Little Menu Example

REBOL [Title: "Simple Menu Example"]

view center-face gui: layout [
    size 400x300
    at 100x100 H3 "You selected:"
    display: field
    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]
        ]
    ]
    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
        ]
    ]
    text bold "Help" [
        file-menu/offset: -200x-200
        show file-menu
        ; PUT YOUR CODE HERE:
        set-face display "Help"
    ]
]

4.5 Loops and Conditions - A Simple Data Storage App

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

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:

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
]

4.6 FTP Chat Room

REBOL [title: "FTP Chat Room"]

webserver: to-url request-text/title/default {
    URL of text file on your server:} "ftp://user:pass@site.com/chat.txt"
name: request-text/title "Enter your name:"
cls: does [prin "^(1B)[J"]
write/append webserver rejoin [now ": " name " has entered the room.^/"]
forever [
    current-chat: read webserver 
    cls
    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
    sent-message: copy rejoin [
        name " says: "
        entered-text: ask "You say:  "
    ]
    switch/default entered-text [
        "quit" [break] 
        "clear" [
            if/else request-pass = ["secret" "password"] [
                write webserver ""
            ] [
                alert {
                    You must know the administrator password to clear
                    the room!
                }
            ]
        ]
        "room" [
            write/append webserver rejoin [
                now ": " name " has left the room." newline
            ]
            webserver: to-url request-text/title/default {
                New Web Server Address:} to-string webserver
            write/append webserver rejoin [
                now ": " name " has entered the room." newline
            ]
        ]
        "lock" [
            alert {The program will now pause for 5 seconds. 
                You'll need the correct username and password 
                to continue.
            }
            pause-time: now/time + 5  
            forever [
                if now/time = pause-time [
                    while [
                        request-pass <> ["secret" "password"]
                    ][
                        alert "Incorrect password - look in the source!"
                    ]
                    break
                ]
            ]
        ]
    ][
        if entered-text <> "" [
            write/append webserver rejoin [sent-message newline]
        ]
    ]
]
cls print "Goodbye!" 
write/append webserver rejoin [now ": " name " has closed chat." newline]
wait 1

4.7 Image Effector

REBOL [] 
effect-types: [
    "Invert" "Grayscale" "Emboss" "Blur" "Sharpen" "Flip 1x1" "Rotate 90"
    "Tint 83" "Contrast 66" "Luma 150" "None"
]
either exists? %/c/play_sound.r [
    do %/c/play_sound.r
    sound-available: true
][
    sound-available: false
]
image-url: to-url request-text/title/default {
    Enter the URL of an image to use:} trim {
    http://rebol.com/view/demos/palms.jpg}
gui: [
    across 
    space -1 
    at 20x2 choice 160 tan trim {
        Save Image} "View Saved Image" "Download New Image" trim {
            -------------} "Exit" [
        if value = "Save Image" [ 
            filename: to-file request-file/title/file/save trim {
                Save file as:} "Save" %/c/effectedimage.png
            save/png filename to-image picture 
        ]
        if value = "View Saved Image" [
            view-filename: to-file request-file/title/file {
                View file:} "" %/c/effectedimage.png
            view/new center-face layout [image load view-filename]

        ]
        if value = "Download 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}
            picture/image: new-image
            show picture
        ]
        if value = "-------------" []  ; don't do anything
        if value = "Exit" [
            if sound-available = true [
                play-sound %/c/windows/media/tada.wav
            ]
            quit
        ]         
    ]
    choice tan "Info" "About" [
        alert "Image Effector - Copyright 2005, Nick Antonaccio"
    ] 
    below 
    space 5 
    pad 2 
    box 550x1 white
    pad 10
    vh1 "Double click each effect in the list on the right:"
    return across
    picture: image load image-url
    text-list data effect-types [
        current-effect: value 
        picture/effect: to-block form current-effect 
        show picture
    ]
]
view/options center-face layout gui [no-title]

4.8 Guitar Chord Diagram Maker

REBOL [Title: "Guitar Chord Diagram Maker"]

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=
}
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
    ]
]
gui: [
    backdrop white
    currentfretboard: image fretboard 255x300
    currentbar: image barimage 240x15 feel movestyle
    text "INSTRUCTIONS:" underline
    text "Drag dots and other widgets onto the fretboard."
    across    
    text "Resize the fretboard:"
    tab 
    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
    button "Save Diagram" [
        filename: to-file request-file/save/file "1.png"
        save/png filename to-image currentfretboard
    ]
    tab
    button "Print" [
        filelist: sort request-file/title "Select image(s) to print:"
        html: copy "<html><body>"
        foreach file filelist [
            append html rejoin [
                {<img src="file:///} to-local-file file {">}
            ]
        ]
        append html [</body></html>]
        write %chords.html trim/auto html
        browse %chords.html 
    ]
]
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]]
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

4.9 Shoot-Em-Up Video Game

REBOL [title: "VID Shooter"]

score: 0   speed: 10   lives: 5   fire: false   random/seed now/time
alert "[SPACE BAR: fire] | [K: move left] | [L: move right]"
do game: [
    view center-face layout [
        size 600x440
        backdrop black
        at 246x0 info: text tan rejoin ["Score: " score " Lives: " lives]
        at 280x440 x: box 2x20 yellow
        at (as-pair -50 (random 300) + 30) y: btn 50x20 orange
        at 280x420 z: btn 50x20 blue
        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 + 25) 440
            ]
        ]
        box 0x0 rate speed feel [
            engage: func [f a e] [
                if a = 'time [
                    if fire = true [x/offset: x/offset + 0x-30]
                    if x/offset/2 < 0 [x/offset/2: 440  fire: false]
                    show x
                    y/offset: y/offset + as-pair 
                        ((random 20) - 5) ((random 10) - 5)
                    if y/offset/1 > 600 [
                        lives: lives - 1
                        if lives = 0 [
                            alert join "GAME OVER!!! Final Score: " score
                            quit
                        ]
                        alert "-1 Life!"   unview   do game
                    ]
                    show y
                    if within? x/offset (y/offset - 5x5) 60x30 [
                        alert "Ka-blammmm!!!"
                        score: score + 1   speed: speed + 5  fire: false
                        unview   do game
                    ]
                ]
            ]
        ]
    ]
]

4.10 Catch Game

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
    ]]]
]

4.11 Blogger

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)]
]

4.12 Listview Multi Column Data Grid Example

REBOL [title: "Listview Data Grid"]

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
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 not exists? %database.db [write %database.db {[][]}]
database: load %database.db

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
    ]
]

4.13 Thumbnail Maker

REBOL [Title: "Thumbnail Maker"]

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" [
        some-images: request-file/title trim/lines {Hold
            down the [CTRL] key to select multiple images:} ""
        if some-images = none [return] 
        foreach single-image some-images [
           append the-images/text single-image
           append the-images/text "^/"
        ]
        show the-images
    ]
    btn "Create Thumbnail Mosaic" [
        y-size: to-integer height/text
        mosaic-size: to-integer width/text
        padding: to-integer padding-size/text
        if error? try [background-color: to-tuple background-color][
            background-color: white
        ]
        images: copy parse/all the-images/text "^/"
        if empty? images [alert "No images selected." break]
        mosaic: compose [
            backcolor (background-color) space (padding) across
        ]
        foreach picture images [
             flash rejoin ["Resizing " picture "..."]
             original: load to-file picture
             unview
             either original/size/2 > y-size [
                 new-x-factor: y-size / original/size/2
                 new-x-size: round original/size/1 * new-x-factor
                 new-image: to-image layout/tight [
                     image original as-pair new-x-size y-size
                 ]
                 append mosaic compose [image (new-image)]
             ][
                 append mosaic compose [image (original)]
             ]
             current-layout: layout/tight mosaic
             if current-layout/size/1 > mosaic-size [
                 insert back back tail mosaic 'return
             ]
        ]
        filename: to-file request-file/file/save "mosaic.png"
        save/png filename (to-image layout mosaic)
        view/new layout [image load filename]
    ]
]

5. Additional Topics

5.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.

To create a new object prototype in REBOL, use the following syntax:

label: make object! [object definition]

Object definitions 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
]

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."
    ]
]

5.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. 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
        ]
    ]
]

In order to access all the values in the system object, it's essential to understand object notation:

get in system/components/graphics 'date

5.2 Ports

REBOL "ports" provide a single way to handle many types of data input and output with series functions (POP email boxes, FTP directories, local text files, TCP network connections, keyboard input buffers, sound I/O, console I/O, etc.). Ports are created using the "open" function:

my-files: open ftp://user:pass@site.com/public_html/

Traverse the above files 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

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 such a folder, but ports offer more specific access to and control of individual items. For example, to read one email from an email 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

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

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

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 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:

write %temp.txt ""
print read %temp.txt
temp: open %temp.txt
append temp "1234"
print temp/state/inBuffer
print read %temp.txt
update temp
print read %temp.txt
temp: head temp
insert temp "abcd"
print temp/state/inBuffer
print read %temp.txt
close temp
print read %temp.txt
append temp "1q2w3e4r"  ; (error, because the port is now closed)

"Help open" displays the following list of refinements that help deal appropriately with port data:

/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)

5.2.1 Console Email Application

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]
        ]
    ]
]

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]
]

5.2.2 Network Ports

One important use of ports is for transferring data via network connections (TCP and UDP "sockets"). Potential ports range from 0 to 65535.

The following 2 scripts demonstrate 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. 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, and replace the word "localhost" with that number. You can obtain the IP address of your local computer with the following REBOL code:

read join dns:// (read dns://)

Here's the SERVER program. Be sure to run it before starting the client, or you will receive an error:

server: open/lines tcp://:55555  ; expect full lines of text
wait server  ; wait for a connection to the above port
connection: first server  ; label the first connection to the port
data: first connection  ; get the inserted data
alert rejoin ["Text received: " data]  ; display the inserted data
close server

Here's the CLIENT program. Run it in a separate instance of the REBOL interpreter, after the above program has been started:

server-port: open/lines tcp://localhost:55555  ; or replace with IP
insert server-port "Hello Mr. Watson."
close server-port

The scripts below continuously loop to send, receive, and display messages transferred from client(s) to the server. Run this server 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
    ]
]

Run this client only after the server program has been started, in a separate instance of the REBOL interpreter (or on a separate computer, replacing "localhost" with the IP address of the server):

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
    ]
]

REBOL servers 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:

; 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 can act as either server or client, and can send messages 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:

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 (a truly unbelievable example!):

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]
]

Here's an extended version that uploads your chosen user name, WAN/LAN IP, and port numbers to an FTP server. 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]
]

Only server machines need to have an exposed IP address or open router/firewall port. Client machines can be located behind a router or firewall, without any forwarded incoming ports.

5.2.3 Peer-to-Peer Instant Messenger

REBOL [Title: "Peer-to-Peer Instant Messenger"]

connected: false
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
     text bold "Save Chat" [
        filename: to-file request-file/title/file/save trim {
            Save file as:} "Save" %/c/chat.txt
        write filename display/text 
    ]
    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://
        ]
    ]
     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
    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
        ]
    ]
    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]
        ]
        focus entry
        connected: true
        forever [
            wait port
            foreach msg any [copy port []] [
                display/text: rejoin [
                    ">>>  "msg newline display/text]
            ]
            show display
        ]
    ]
    return  display: area "" 537x500
    return  entry: field 428  ; the numbers are pixel sizes
    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.

5.2.4 Transferring Binary Files Through TCP Network Sockets:

Sending binary files requires that the length of the file be transmitted before sending the file:

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 just records sound from mic to .wav file, then transfers the wave file to another IP (where the same program is running). 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:

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
]]

5.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 "^/"

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 .csv data into REBOL blocks. 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 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")
]

5.3.1 PARSE's 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:

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. 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:

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

5.4 2D Drawing, Graphics, and Animation

REBOL includes a built-in "draw" dialect to handle graphics and animation. 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 (although, you can draw on any widget). "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.):

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 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 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
        ]
    ]
]

5.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
        ]
    ]
]

In this example, the "feel" function is used to update the screen every 10th of a second ("rate 0:0:0.1"):

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 simple paint program. 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]]
]

This example shows how to scale and distort images by indicating 4 coordinate 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 image scaling with some animation:

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
        ]
    ]
]

5.5 3D Graphics with r3D

The "r3D" modeling engine by Andrew Hoadley is built entirely from native REBOL 2D draw functions:

do to-string decompress 64#{
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
}

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. 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 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 (http://local.wasp.uwa.edu.au/~pbourke/dataformats/oogl/#OFF). A number of OFF example objects are available at http://www.mpi-sb.mpg.de/~kettner/proj/obj3d/.

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:

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
    ]
]

5.5.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):

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!"
   ]
]

5.6 Multitasking

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)

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 example creates a video stream by repeatedly downloading and displaying images from a given webcam URL. 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 separate webcam videos 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. The evaluation of code is not concurrent. In the following example, 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.

5.7 Using DLLs and Shared Code Files in REBOL

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.

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 []

lib: load/library %kernel32.dll
play-sound: make routine! [
    return: [integer!] pitch [integer!] duration [integer!]
] lib "Beep"
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 using the Windows MCI API:

lib: load/library %winmm.dll
mciExecute: make routine! [ 
    command [string!]
    return: [logic!] 
] lib "mciExecute"
file: to-local-file to-file request-file/save/title/file "Save as:" {
    } %rebol-recording.wav
mciExecute "open new type waveaudio alias buffer1 buffer 6"
mciExecute "record buffer1"
ask "RECORDING STARTED (press [ENTER] when done)...^/"
mciExecute "stop buffer1"
mciExecute join "save buffer1 " file
free lib
print "Recording complete.  Here's how it sounds:^/"
insert port: open sound:// load to-rebol-file file wait port close port
print "DONE.^/"
halt

This example demonstrates how to play AVI video files using winmm.dll. The video codec in the demo video is MS-CRAM (Microsoft Video 1), and the audio format is PCM.

lib: load/library %winmm.dll
mciExecute: make routine! [c [string!] return: [logic!]] lib "mciExecute"
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
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"
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. 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
load-dic: make routine! [
    a [string!] 
    return: [none]
] lib "Dictionary_Load"
check-word: make routine! [
    a [string!]
    b [integer!]
    return: [integer!]
] lib "Dictionary_Check"
load-dic ""
response: check-word check-me 0
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:

REBOL []
write/binary %mp3.dll read/binary http://musiclessonz.com/mp3.dll
lib: load/library %mp3.dll
play-mp3: make routine! [a [string!] return: [none]] lib "playfile"
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 (http://www.autoitscript.com/autoit3/downloads.php), to move the mouse around the screen.:

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

You can add the following code 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 []

avicap32.dll: load/library %avicap32.dll
user32.dll: load/library %user32.dll
get-focus: make routine! [return: [int]] user32.dll "GetFocus"
set-caption: make routine! [
    hwnd [int] a [string!]  return: [int]
] user32.dll "SetWindowTextA"
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"

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
    ]
]

hwnd-set-title: get-focus
set-caption hwnd-set-title "Web Camera"

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

do-events

5.8 Web Programming and the CGI Interface

The following HTML example contains a form with a text input field and a submit button:

<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>

You can use the data entered into any such form by pointing the "form action" address to the URL at which a specific REBOL script is located. A script such as the following can process and use the submitted form data:

#!/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.

5.8.1 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 ways 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:

#!/home/your_user_path/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: decode-cgi system/options/cgi/query-string

print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}

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>}
]

5.8.2 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 script that allows you to type REBOL code into an HTML text area, and have that code execute directly on your web server. You can use it to run REBOL code, or to call shell programs directly on your web site. 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 above 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">
}

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 addition form data,
        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 (http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlKVSQ):

#! /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

5.8.3 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, 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 (http://rebol.com/platforms.html). 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>}

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/docs/rsp-api.html.

5.9 WAP - Cell Phone Browser CGI Apps

To format the output of CGI scripts for cell phones, output should be written in WAP ("Wireless Application Protocal") syntax (WML), instead of HTML (http://www.w3schools.com/WAP/wml_reference.asp).

Here's a basic WAP CGI script demonstrating stock WML code. The first 5 lines should be memorized. The last lines demonstrate some other important WML 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">^/}

print {<wml><card id="1" title="Current Time"><p>}
print now
print {</p></card></wml>}
quit

The following script converts text data output by another script on the web site to WAP format, so that it can be read on cell phones. Because WAP syntax is a bit more strict than HTML, some HTML tags must be removed (replaced) from the source script output. 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 more useful version of the above script which allows users to specify the file to be converted, right in the URL of the 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>}

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 a WAP enabled 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
]

5.10 REBOL as a Browser Plugin

The REBOL browser plugin allows you to run complete, graphical REBOL scripts on your web pages. To use it, just include the necessary object code in your HTML. 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!"]
]

The above script must be run in Internet Explorer on MS Windows. In order to use the plugin in Mozilla based browsers, 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/plugin_test_mozilla.html.

5.11 Using Databases

The most recent releases of REBOL, and 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. Every time you access a MySQL database, you need to "do" the module that was unpacked from the link above:

do %mysql-r107/mysql-protocol.r

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
        ] 
    ] 
]

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 .

5.12 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 (http://www.dobeash.com/rebgui.html) 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"]

do load-thru http://re-bol.com/cyphre_menu_and_tab_panel.r

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 save 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.

5.13 Multi Column GUI Text Lists (Data Grids)

REBOL's GUI dialect has a built in "list" widget which is fairly complicated to use. RebGUI's "table" widget, or the listview.r module covered earlier are recommended instead. A complete tutorial about REBOL's list widget is available at http://re-bol.com. Here is an example to demonstrate how the list widget can be used:

REBOL [title: "List Widget Example"]

x: copy []   random/seed now/time   ; generate 5000 rows of random data:
repeat i 5000 [
    append/only x reduce [random "asdfqwertyiop" form random 1000 form i]
]  y: copy x
Alert help-txt: {Be sure to try the following features:  1) Resize the GUI
    window to see the list automatically adjust to fit  2) Click column
    headers to sort by field  3) Use the arrow keys and page-up/page-down
    keys to scroll  4) Use the Insert, Delete and "M" keys to add, remove
    and move rows (by default, at the currently highlighted row)  5) Click
    the small "r" header button in the top right corner to reset the list
    back to its original values  6) Click any individual data cell to edit
    the selected value.}
sort-column: func [field] [
    either sort-order: not sort-order [
        sort/compare x func [a b] [(at a field) > (at b field)]
    ] [
        sort/compare x func [a b] [(at a field) < (at b field)]
    ]  
    show li
]
key-scroll: func [scroll-amount] [
    s-pos: s-pos + scroll-amount
    if s-pos > (length? x) [s-pos: length? x]
    if s-pos < 0 [s-pos: 0]
    sl/data: s-pos / (length? x)  
    show li  show sl
]
resize-grid: func [percentage] [
    gui-size: system/view/screen-face/pane/1/size ; - 10x0
    list-size/1: list-size/1 * percentage
    list-size/2: gui-size/2 - 95
    t-size: round (list-size/1 / 3)
    sl-size: as-pair 16 list-size/2
    unview/only gui view/options center-face layout gui-block [resize]
]
resize-fit: does [
    gui-size: system/view/screen-face/pane/1/size
    resize-grid (gui-size/1 / list-size/1 - .1)
]
insert-event-func [either event/type = 'resize [resize-fit none] [event]]
gui-size: system/view/screen-face/size - 0x50
list-size: gui-size - 60x95
sl-size: as-pair 16 list-size/2
t-size: round (list-size/1 / 3)
s-pos: 0  sort-order: true  ovr-cnt: none  svv/vid-face/color: white
view/options center-face gui: layout gui-block: [
    size gui-size  across
    btn "Smaller" [resize-grid .75]
    btn "Bigger" [resize-grid 1.3333]
    btn "Fit" [resize-fit]
    btn #"^~" "Remove" [attempt [
        indx: to-integer request-text/title/default "Row to remove:" 
            form to-integer ovr-cnt
        if indx = 0 [return]
        if true <> request rejoin ["Remove: " pick x indx "?"] [return]
        remove (at x indx)  show li
    ]]
    insert-btn: btn "Add" [attempt [
        indx: to-integer request-text/title/default "Add values at row #:"
            form to-integer ovr-cnt
        if indx = 0 [return]
        new-values: reduce [
            request-text request-text (form ((length? x) + 1))
        ]
        insert/only (at x indx) new-values  show li
    ]]
    btn #"m" "Move" [
        old-indx: to-integer request-text/title/default "Move from row #:"
            form to-integer ovr-cnt
        new-indx: to-integer request-text/title "Move to row #:"
        if ((new-indx = 0) or (old-indx = 0)) [return]
        if true <> request rejoin ["Move: " pick x old-indx "?"] [return]
        move/to (at x old-indx) new-indx  show li
    ]
    btn "Save" [save to-file request-file/save x]
    btn "Load" [y: copy x: copy load request-file/only  show li]
    btn "Read Me" [alert help-txt]
    btn "View Data" [editor x]
    return  space 0x0
    style header button as-pair t-size 20 black white bold
    header "Random Text" [sort-column 1]
    header "Random Number" [sort-column 2] 
    header "Unique Key" [sort-column 3]
    button black "r" 17x20 [if true = request "Reset?"[x: copy y show li]]
    return
    li: list list-size [
        style cell text t-size feel [
            over: func [f o] [
                if (o and (ovr-cnt <> f/data)) [ovr-cnt: f/data show li]
            ]
            engage: func [f a e] [
                if a = 'up [
                    f/text: request-text/default f/text show li
                ]
            ]
        ]             
        across  space 0x0
        col1: cell blue
        col2: cell
        col3: cell red
    ] supply [
        either even? count [face/color: white] [face/color: 240.240.255]
        count: count + s-pos
        if none? q: pick x count [face/text: copy "" exit]
        if ovr-cnt = count [face/color: 200.200.255]
        face/data: count
        face/text: pick q index
    ]
    sl: scroller sl-size [s-pos: (length? x) * value  show li]
    key keycode [up] [key-scroll -1]
    key keycode [down] [key-scroll 1]
    key keycode [page-up] [key-scroll -20]
    key keycode [page-down] [key-scroll 20]
    key keycode [insert] [do-face insert-btn 1]
] [resize]

The following examples are presented to demonstrate how to quickly display and manipulate small multicolumn lists of data using only native GUI widgets (no list widgets required):

x: copy [] for i 1 179 1 [append x reduce [i random "abcd"]]

grid: copy [across space 0]  ; the GUI block containing the grid of fields
forskip x 2 [append grid compose [field (form x/1)field (form x/2)return]]
view center-face layout [across
    g: box 400x200 with [pane: layout/tight grid pane/offset: 0x0]
    scroller [g/pane/offset/y: g/size/y - g/pane/size/y * value show g]
]

The next example demonstrates how to take two columns of data (blocks) and combine them into a single block that can be displayed using the layout above:

x: copy [] 
block1: copy system/locale/months  block2: copy system/locale/days
for i 1 (max length? block1 length? block2) 1 [
    append x either g: pick block1 i [g] [""]
    append x either g: pick block2 i [g] [""]
]

grid: copy [across space 0]
forskip x 2 [append grid compose [field (form x/1)field (form x/2)return]]
view center-face layout [across
    g: box 400x200 with [pane: layout/tight grid pane/offset: 0x0]
    scroller [g/pane/offset/y: g/size/y - g/pane/size/y * value show g]
]

The next example demonstrates how to change the look of the grid layout, and how to obtain a block containing all the data displayed in the grid, including user edits. To clarify visual separation of row data, an alternating color is assigned to each row in the grid:

x: copy [] for i 1 179 1 [append x reduce [i random "abcd"]]

grid: copy [origin 0x0 across space 0x0]
forskip x 2 [
    color: either (remainder ((index? x) - 1) 4) = 0 [white][wheat]
    append grid compose [
        field 180 (form x/1) (color) edge none
        field 180 (form x/2) (color) edge none return
    ]
]
view center-face layout [
    across space 0  
    g: box 360x200 with [pane: layout grid pane/offset: 0x0]
    scroller[g/pane/offset/y: g/size/y - g/pane/size/y * value / 2 show g]
    return box 1x10 return  ; just a spacer
    btn "Get Data Block (INCLUDING USER EDITS)" [
        q: copy [] foreach face g/pane/pane [append q face/text] editor q
    ]
]

The next example demonstrates a number of features that allow entering, editing, and storing columns of data:

x: copy [] for i 1 179 1 [append x reduce [i random "abcd"]]

update: does [q: copy [] foreach face g/pane/pane [append q face/text]]
do qq: [grid: copy [across space 0]
forskip x 2 [append grid compose [
    field (form x/1) 40 edge none 
    field (form x/2) 260 edge [size: 1x1] return
]]
view center-face gui: layout [across space 0
    g: box 300x290 with [pane: layout/tight grid pane/offset: 0x0]
    slider 16x290 [
        g/pane/offset/y: g/size/y - g/pane/size/y * value show g
    ]
    return btn "Add" [
        row: (to-integer request-text/title "Insert at Row #:") * 2 - 1
        update insert at q row ["" ""] x: copy q unview do qq
    ]
    btn "Remove" [
        row: (to-integer request-text/title "Row # to delete:") * 2 - 1
        update remove/part (at q row) 2 x: copy q unview do qq
    ]
    btn "Col 1" [update editor extract q 2]
    btn "Col 2" [update editor extract/index q 2 2]
    btn "Save" [update save to-file request-file/save q]
    btn "Load" [x: load to-file request-file do qq]
    btn "History" [
        m: copy "ITEMS YOU'VE EDITED:^/^/" update for i 1 (length? q) 1 [
            if (to-string pick x i) <> (to-string pick q i) [
                append m rejoin [pick x i " " pick q i newline]
            ]
        ] editor m 
    ]
]]

This final example clarifies how to add additional columns, how to use GUI widgets other than fields to display the data (text widgets, in this case), how to make the widgets perform any variety of actions, and how to get data from the grid when not every widget has text on its face. It also demonstrates some additional changes to the look of the grid:

x: copy [] for i 1 99 1 [append x reduce [i random 99x99 random "abcd"]]

grid: copy [origin 0x0 across space 0x0]
forskip x 3 [
    append grid compose [
        b: box 520x26 either (remainder((index? x)- 1)6)= 0 [white][beige]
        origin b/offset
        text bold 180 (form x/1)
        text 120 center blue (form x/2) [alert face/text]
        text 180 right purple (form x/3) [face/text: request-text] return
        box 520x1 green return
    ]
]
view center-face layout [
    across space 0  
    g: box 520x290 with [pane: layout grid pane/offset: 0x0]
    scroller 16x290 [
        g/pane/offset/y: g/size/y - g/pane/size/y * value / 2 show g
    ]
    return box 1x10 return  ; just a spacer
    btn "Get Data Block" [
        q: copy [] 
        foreach face g/pane/pane [
            if face/style = 'text [append q face/text]
        ]
        editor q
    ]
]

5.14 RebGUI

REBOL's VID dialect ("view layout []"), is one of the language's most attractive features. "RebGUI" is a third party GUI toolkit built on REBOL/View which replicates many of the basic components in VID, and upgrades/adds to the concept with many desirable features:

  1. Modern look and feel.
  2. Many powerful and useful new widgets and built-in functions: resizable tables (data grids) with automatic column sorting, trees, menus, tab and scroll panels, group boxes, tool-bars, spreadsheet, pie-chart and chat widgets, new requestors, native undo/redo, spellcheck, and translate functions (with many provided language dictionaries) for text widgets, etc.
  3. Simple and elegant syntax (similar to VID).
  4. Full documentation and demo code for all widgets.
  5. Super simple notation to handle automatic alignment and layout of widgets in resized windows.
  6. Config file to easily manage user settings for global UI sizes, colors, behaviors, and effects of all widgets. A built-in native requestor is also provided to adjust all these settings.
  7. Automatic handling of window close events.
  8. User assignable function key actions.
  9. Easy, automatic handling of multiple user languages.
  10. Well designed object structure to access every widget, function, and feature (and containing all necessary help information, built in).
  11. The entire system compresses to just over 30k.

For most types of applications, RebGUI provides a single, simple, integrated way to build applications with all the most commonly needed user interface features.

RebGUI is available at http://www.dobeash.com/download.html. All you need to use RebGUI is %rebgui.r. Copy it to an accessible folder and include the line "do %rebgui.r" (with its path, if necessary), and then you can use all the built in widgets and functions.

Here's a minimal RebGUI example:

do %rebgui.r
display "Test" [button "Click Me" [alert "Clicked"]]
do-events

Notice that "view layout" has been replaced with "display". This function always requires some title text. Notice also that "do-events" must be included after your RebGUI code to activate the GUI.

Once you've included %rebgui.r, you can try any of the built-in widgets and functions:

display "" [area] do-events  ; the text area widget

Notice that the area widget above has built-in undo/redo features using [CTRL]-Z and [CTRL]-Y (REBOL's native "view layout [area]" does NOT have any undo/redo capability). A built-in spellchecker can also be activated using [CTRL]-S. To use the spellchecker, you need to download a dictionary from http://www.dobeash.com/RebGUI/dictionary and unzip it into the /dictionary subdirectory of wherever %rebgui.r is located.

Here are a few other widgets built into RebGUI:

do %rebgui.r  ; be sure to include the path, if necessary

display "Pie Chart" [pie-chart data ["VID" yellow 19 "RebGUI" red 81]]
do-events

display "Spreadsheet" [
    sheet options [size 7x7] data [a1 "very " a2 "cool" a3 "=join a1 a2"]
]
do-events

display "Chat" [
    chat data ["Nick" blue "I like RebGUI" yellow 20-sep-2009/1:00]]
do-events

display/maximize "Menu" [
    menu data [
        "File" [
             "Open" [request-file]
             "Save" [request-file]
         ] 
        "About" ["Info" [alert "RebGUI is great!"]]
    ]
]
do-events

You can run the RebDoc.r program to see the syntax required to use any of the other RebGUI widgets, requestors and functions.

The "/close" refinement of the "display" function lets you set any action(s) you want to run when a GUI window is shut down:

display/close "" [area] [question "Really Close?"] do-events

Be sure to try the "request-ui" requestor function. It lets users adjust the global settings for the overall look and feel of layouts created with RebGUI. Settings are saved in the file %ui.dat, in the current working directory.

request-ui

RebGUI includes a variety of "span directives" to easily automate resizing of widgets:

These directives automatically set the initial size of a widget:

#L - align the right hand edge of the widget with the adjacent edge
#V - align the base edge of the widget with the adjacent edge
#O - align the left hand edge of the widget with the adjacent edge

("adjacent edge" is the edge of the adjacent widget, or the edge of
the GUI, if there is no adjacent widget.)

These directives automatically adjust the size and position of
a widget when the GUI is resized:

#H - stretch or shrink the widget to fit the window height
#W - stretch or shrink the widget to fit the window width
#X x - move the widget x number of pixels to the right
#Y y - move the widget y number of pixels downward

Here's an example of an area widget that stretches and shrinks to fit a resized GUI window:

display "" [area #HW] do-events

Here's a fully functional, resizable text editor application, with built-in undo/redo, spell checking, and close event handling:

do %rebgui.r
display/maximize/close "Text Editor" [
    menu #LHW data [
        "File" [
             "Open" [x/text: read to-file request-file show x]
             "Save" [write to-file request-file/save x/text]
         ] 
    ] return
    x: area #LHW
] [question "Really Close?"] do-events

Here's a useful spreadsheet application, with save, load, print and data view features:

do %rebgui.r
display "Spreadsheet" [
    x: sheet options [size 3x3 widths [8 8 10]] data [
        A1 32 A2 12 A3 "=a1 + a2" A4 "=1.06 * to-integer a3"
    ]
    return 
    button "Save" [
        x/save-data
        save to-file request-file x/data
    ]
    button "Load" [
        x/load-data load to-file request-file
    ]
    button "View" [
        x/save-data
        alert form x/data
    ]
    button "Print" [
        save/png %sheet.png to image! x
        browse %sheet.png  ; or call %sheet.png
    ]
] 
do-events

This example demonstrates how to use tab panels to maximize screen real estate, and a variety of other useful techniques:

display "Tab Panel" [
    main-screen: tab-panel data [
        "Spreadsheet" [
            x: sheet data [
                A1 32 A2 12 A3 "=a1 + a2" A4 "=1.06 * to decimal! a3"
            ]
            a: area
            reverse
            button -1 " Show Data " [x/save-data set-text a x/data]
            button -1 " Quit! " [quit]
        ]
        "VID style"  [
            style -1 data [text bold "Back to Spreadsheet" [
                main-screen/select-tab 1
            ]]
        ]
        action [wait .2 face/color: 230.230.230]  "Text" [
            text "Tabs are a great way to maximize screen real estate."
        ]
        action [wait .2 set-focus z]  "Fields" [
            y: field
            z: field "Edit me"
        ]
    ]
] 
do-events

To really get to know RebGUI, explore its main object "ctx-rebgui":

? ctx-rebgui

The "ctx-rebgui" object is set up much like REBOL's built-in "system/view/vid" object. You can explore it using path notation. Notice that built-in help is included in the "tip" path of each widget:

? ctx-rebgui/widgets/tree/tip

Here's a quick and dirty way to view built-in help for all the RebGUI widgets:

foreach i (find first ctx-rebgui/widgets 'anim) [
    do compose/deep [print rejoin[i" - "(ctx-rebgui/widgets/(i)/tip)"^/"]]
]

The main RebGUI user guide is available at http://www.dobeash.com/RebGUI/user-guide.html, the cookbook is at http://www.dobeash.com/RebGUI/cookbook.html. Be sure to examine the code in tour.r, and get to know your way around ctx-rebgui. You'll likely find that RebGUI is the best choice for GUI layout in many situations.

The example below is an implementation of the "Card File" program presented earlier, using RebGUI instead of VID:

REBOL []

do load-thru http://re-bol.com/rebgui.r

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

display "RebGUI Card File" [
    text 20 "Select:"
    names: drop-list #LW data (sort extract copy database 4) [
        marker: find database pick names/data names/picked
        set-text n copy first marker
        set-text a copy second marker
        set-text p copy third marker
        set-text o copy fourth marker
    ]
    after 2
    text 20 "Name:"        n: field #LW ""
    text 20 "Address:"     a: field #LW ""
    text 20 "Phone:"       p: field #LW ""
    after 1 text "Notes:"  o: area #LW ""
    after 3
    button -1 "Save" [
        if (n/text = "") [alert "You must enter a name." return]
        if find (sort extract copy database 4) copy n/text [
            either true = question "Overwrite existing record?" [
               remove/part (find database n/text) 4
            ] [return]
        ]
        database: repend database [
            copy n/text copy a/text copy p/text copy o/text
        ]
        save %data.txt database
        set-data names (sort extract copy database 4)
        set-text names copy n/text
    ]
    button -1 "Delete" [
        if true = question rejoin ["Delete " copy  n/text "?"] [
            remove/part (find database n/text) 4
            save %data.txt database
            set-data names (sort extract copy database 4)
            set-values face/parent-face ["" "" "" "" ""]
        ]
    ]
    button -1 "New" [
            set-values face/parent-face ["" "" "" "" ""]
    ]
]
do-events

Here's a useful text/code editor:

REBOL []

unless exists? %ui.dat [
    write %ui.dat read http://re-bol.com/ui-editor.dat
]
do load-thru http://re-bol.com/rebgui.r    ; Build#117
; do %rebgui.r

filename: %temp.txt
make-dir %./edit_history/

backup: does [
    if ((length? x/text) > 0) [
        write rejoin [
            %./edit_history/ 
             last split-path filename 
             "_" now/date "_"
             replace/all form now/time ":" "-"
        ] x/text
    ]
]
ctx-rebgui/on-fkey/f5: does [
    backup
    write filename x/text
    launch filename
]

display/maximize/close "RebGUI Editor" [
    tight
    menu #LW data [
        "File" [
            "  New  " [
                if true = question "Erase Current Text?" [
                    backup
                    filename: %temp.txt set-text x copy ""
                ]
            ]
            "  Open  " [
                filetemp: to-file request-file/file filename
                if filetemp = %none [return]
                backup
                set-text x read filename: filetemp
            ]
            "  Save  " [
                backup
                write filename x/text
            ]
            "  Save As  " [
                filetemp: to-file request-file/save/file filename
                if filetemp = %none [return]
                backup
                write filename: filetemp x/text
            ]
            "  Save and Run  " [
                backup
                write filename x/text 
                launch filename
            ]
            "  Print  "  [
                write %./edit_history/print-file.html rejoin [
                    {<}{pre}{>} x/text {<}{pre}{>}
                ]
                browse %./edit_history/print-file.html
            ]
            "  Quit  " [
                if true = question "Really Close?" [backup quit]
            ]
        ]
        "Options" [
            "  Appearance  " [request-ui]
        ]
        "Help" [
            "  Shortcut Keys  " [
                alert trim {
                    F5:       Save and Run
                    Ctrl+Z:   Undo
                    Ctrl+Y:   Redo
                    Esc:      Undo All
                    Ctrl+S:   Spellcheck
                }
            ]
        ] 
    ] return
    x: area #LHW
] [
    if true = question "Really Close?" [backup quit]
]

do-events

Here's a user management script, inspired by the tutorial at http://snappmx.com:

REBOL [title: "RebGUI User List Demo"]

do load-thru http://re-bol.com/rebgui.r    ; Build#117  ; do %rebgui.r
unless exists? %snappmx.txt [
    save %snappmx.txt [
        "user1" "pass1" "Bill Jones" "%bill--site--com" "Bill LLC" 
        "user2" "pass2" "John Smith" "%john--mail--com" "John LLC"
    ]
]
database: load %snappmx.txt
login: request-password
found: false
foreach [userid password name email company] database [
    either (login/1 = userid) and (login/2 = password) [found: true] []
]
if found = false [alert "Incorrect Login." quit]
add-record: does [
    display/dialog "User Info" [
        text 20 "User:" f1: field return
        text 20 "Pass:" f2: field return
        text 20 "Name:" f3: field return
        text 20 "Email:" f4: field return
        text 20 "Company:" f5: field reverse
        button -1 #XY " Clear " [clear-fields]
        button -1 #XY " Add " [add-fields]
    ]
]
edit-record: does [
    display/dialog "User Info" [
        text 20 "User:" f1: field (form pick t/selected 1) return
        text 20 "Pass:" f2: field (form pick t/selected 2) return
        text 20 "Name:" f3: field (form pick t/selected 3) return
        text 20 "Email:" f4: field (form pick t/selected 4) return
        text 20 "Company:" f5: field (form pick t/selected 5) reverse
        button -1 #XY " Delete " [
            t/remove-row t/picked
            save %snappmx.txt t/data
            hide-popup
        ]
        button -1 #XY " Save " [
            t/remove-row t/picked
            add-fields
            save %snappmx.txt t/data
            hide-popup
        ]
    ]
]
add-fields: does [
    t/add-row reduce [
        copy f1/text copy f2/text copy f3/text copy f4/text copy f5/text
    ]
    save %snappmx.txt copy t/data
]
clear-fields: does [
    foreach item [f1 f2 f3 f4 f5] [do rejoin [{set-text } item {""}]]
]
table-size: system/view/screen-face/size/1 / ctx-rebgui/sizes/cell
display/maximize "Users" [
    t: table table-size #LWH options [
        "" left .0  "" left .0   ; don't show the first 2 fields
        "Name" center .33  "Email" center .34  "Company" center .34
    ] data database [edit-record]
    reverse
    button -1 #XY " Add " [add-record]
]
do-events

Here is a point of sale system (sales checkout, receipt printer, and data storage system) written using RebGUI. Note that the first field in the layout is designed to accept input from a keyboard wedge bar code scanner, with data in the format: item (space) booth (space) price (inserted [ENTER] key character):

REBOL [title: "POINT OF SALE SYSTEM"]

write %posp.db {["username" "password"] ["username2" "password2"]} ; etc.
make-dir %./receipts/
write/append %./receipts/deleted.txt ""  ; create file if not exists

unless exists? %scheme_has_changed [
    write %ui.dat decompress #{
        789C9552CB92A3300CBCE72BBCDCA18084995A7E652A078115F08EB129592C33
        7FBFC24E32CC2387A5EC2A49ED6EB56C267845E5BB3FD8F32FF57250F2CD3060
        ABEAA629E23E95B1CAF8C6AD7A3A1571A5D28813E6D60CA32055752AAAE67751
        97CF3B5003BDB6EA5817CF821E9B8804067E484BE04F34BFB035EE4EACCB5371
        DD9FE044AD8E4FC5751FCE6AFA3E648FD6B62A51516F035731BE78B7B9AAEF49
        3EE2D5693A3CC02CCD63B8F5DB8CC464021A8CBB49066B3492901EB4879E8D77
        B92C74BC1D7CD1E467992DB0D8319CA28B41ABE53D42583D691566E31C521438
        7F9161E844241276780F84BCC117DF2F410E480E7BFCBDB7A697FA407E99F3CE
        BF493787568511919588E631DF5146131F602FFA1F8645B1437D35A2BA85D93B
        F5317A8C9810BF5DC240E6A1F0CF374CE4D790B31F507E45B9E10BD8801122D0
        6633DEEC5E3CFB8BA4C14176AF6D936540066CA6B2DE2F649094C35532361386
        EC0B270D18660B61CC355A78BFFD53ECBD6533DF8A655BCA4AD08A9D366E905E
        4C4B72B71AA7FDDA2AE71D1ECEFF004BE40F38A0030000
    }
]

do http://re-bol.com/rebgui.r

do login: [
    userpass: request-password
    if (length? userpass) < 2 [quit]
    posp-database: to-block read %posp.db
    logged-in: false
    foreach user posp-database [
        if (userpass/1 = user/1) and (userpass/2 = user/2) [
            logged-in: true
        ]
    ]
    either logged-in = true [] [
        alert "Incorrect Username/Password"
        do login
    ]
]
calculate-totals: does [
    tax: .06
    subtotal: 0
    foreach [item booth price] pos-table/data [
        subtotal: subtotal + to decimal! price
    ]
    set-text subtotal-f subtotal
    set-text tax-f (round/to (subtotal * tax) .01)
    set-text total-f (round/to (subtotal + (subtotal * tax)) .01)
    set-focus barcode
]
add-new-item: does [
    if ("" = copy f1/text) or ("" = copy f2/text) or (error? try [
        to-decimal copy f3/text
    ]) [
        alert trim/lines {You must enter a proper Item Description,
            Booth Number, and Price.}
        return
    ]
    pos-table/add-row/position reduce [
        copy f1/text copy f2/text copy f3/text
    ] 1
    calculate-totals
]
print-receipt: does [
    if empty? pos-table/data [
        alert "There's nothing to print." return
    ]
    html: copy rejoin [
        {<html><head><title>Receipt</title></head><body>
        <table width=40% border=0 cellpadding=0><tr><td>
        <h1>Business Name</h1>
        123 Road St.<br>
        City, State 98765<br>
        123-456-7890
        </td></tr></table><br><br>
        <center><table width=80% border=1 cellpadding=5>
        <tr>
        <td width=60%><strong>Item</strong></td>
        <td width=20%><strong>Booth</strong></td>
        <td width=20%><strong>Price</strong></td></tr>}
    ]    
    foreach [item booth price] pos-table/data [
        append html rejoin [
            {<tr><td width=60%>} item 
            {</td><td width=20%>} booth 
            {</td><td width=20%>} price {</td></tr>}
        ]
    ]
    append html rejoin [
        {<tr><td width=60%></td><td width=20%><strong>SUBTOTAL:
        </strong></td><td width=20%><strong>}
        copy subtotal-f/text 
        {</strong></td></tr>}
    ]
    append html rejoin [
        {<tr><td width=60%></td><td width=20%><strong>TAX:
        </strong></td><td width=20%><strong>}
        copy tax-f/text 
        {</strong></td></tr>}
    ]
    append html rejoin [
        {<tr><td width=60%></td><td width=20%><strong>TOTAL:
        </strong></td><td width=20%><strong>}
        copy total-f/text 
        {</strong></td></tr>}
    ]
    append html rejoin [
        {</table><br>Date: <strong>} now/date 
        {</strong>, Time: <strong>} now/time 
        {</strong>, Salesperson: } userpass/1
        {</center></body></html>}
    ]
    write/append to-file saved-receipt: rejoin [
        %./receipts/
        now/date "_"
        replace/all copy form now/time ":" "-"
        "+" userpass/1 
        ".html"
    ] html
    browse saved-receipt
]
save-receipt: does [
    if empty? pos-table/data [
        alert "There's nothing to save." return
    ]
    if allow-save = false [
        unless true = resaving: question trim/lines {
            This receipt has already been saved.  Save again?
        } [
            if true = question "Print another copy of the receipt?" [
                print-receipt
            ]
            return
        ]
    ] 
    if resaving = true [
        resave-file-to-delete: copy ""
        display/dialog "Delete" compose [
            text 150 (trim/lines {
                IMPORTANT - DO NOT MAKE A MISTAKE HERE!  
                Since you've made changes to an existing receipt,
                you MUST DELETE the original receipt.  The original
                receipt will be REPLACED by the new receipt (The
                original data will be saved in an audit history file,
                but will not appear in any future seaches or totals.)
                Please CAREFULLY choose the original receipt to DELETE:
            })
            return
            tl1: text-list 150 data [
                "I'm making changes to a NEW receipt that I JUST SAVED" 
                "I'm making changes to an OLD receipt that I've RELOADED"
            ] [
                resave-file-to-delete: tl1/selected
                hide-popup
            ]
            return
            button -1 "Cancel" [
                resave-file-to-delete: copy "" 
                hide-popup
            ]
        ]
        if resave-file-to-delete = "" [
            resaving: false
            return
        ]
        if resave-file-to-delete = trim/lines {
            I'm making changes to a NEW receipt that I JUST SAVED
        }  [
            the-file-to-delete: saved-file
        ]
        if resave-file-to-delete = trim/lines {
            I'm making changes to an OLD receipt that I've RELOADED
        } [
            the-file-to-delete: loaded-receipt
        ]
        if not question to-string the-file-to-delete [return]
        write %./receipts/deleted--backup.txt read %./receipts/deleted.txt
        write/append %./receipts/deleted.txt rejoin [
            newline newline newline
            to-string the-file-to-delete
            newline newline
            read the-file-to-delete
        ]
        delete the-file-to-delete
        alert "Original receipt has been deleted, and new receipt saved."
        resaving: false
    ]
    if true = question "Print receipt?" [print-receipt]
    saved-data: mold copy pos-table/data
    write/append to-file saved-file: copy rejoin [
        %./receipts/
        now/date "_"
        replace/all copy form now/time ":" "-"
        "+" userpass/1 
        ".txt"
    ] saved-data
    splash compose [
        size: 300x100
        color: sky
        text: (rejoin [{^/      *** SAVED ***^/^/      } saved-file {^/}])
        font: ctx-rebgui/widgets/default-font
    ]
    wait 1
    unview
    allow-save: false
    if true = question "Clear and begin new receipt?" [clear-new]
]
load-receipt: does [
    if error? try [
        loaded-receipt: to-file request-file/file/filter %./receipts/
            ".txt" "*.txt"
    ] [
        alert "Error selecting file"
        return
    ]
    if find form loaded-receipt "deleted" [
        alert "Improper file selection" 
        return
    ]
    if error? try [loaded-receipt-data: load loaded-receipt] [
        alert "Error loading data"
        return
    ]
    insert clear pos-table/data loaded-receipt-data
    pos-table/redraw
    calculate-totals
    allow-save: false
]
search-receipts: does [
    search-word: copy request-value/title "Search word:" "Search"
    ; if search-word = none [return]
    found-files: copy []
    foreach file read %./receipts/ [
        if find (read join %./receipts/ file) search-word [
            if (%.txt = suffix? file) and (file <> %deleted.txt) [
                append found-files file
            ]
        ]
    ]
    if empty? found-files [alert "None found" return]
    found-file: request-list "Pick a file to open" found-files
    if found-file = none [return]
    insert clear pos-table/data (
        load loaded-receipt: copy to-file join %./receipts/ found-file
    )
    pos-table/redraw
    calculate-totals
    allow-save: false
]
clear-new: does [
    if allow-save = true [
        unless (true = question "Erase without saving?") [return]
    ]
    foreach item [barcode f1 f2 f3 subtotal-f tax-f total-f] [
        do rejoin [{clear } item {/text show } item]
    ]
    clear head pos-table/data
    pos-table/redraw
    allow-save: true
]
change-appearance: does [
    request-ui
    if true = question "Restart now with new scheme?" [
        if allow-save = true [
            if false = question "Quit without saving?" [return]
        ]
        write %scheme_has_changed ""
        launch %pos.r  ; EDIT
        quit
    ]
]
title-text: "Point of Sale System"
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
    ]
]

allow-save: true
resaving: false
saved-file: ""
loaded-receipt: ""

screen-size: system/view/screen-face/size
cell-width: to-integer (screen-size/1) / (ctx-rebgui/sizes/cell)
cell-height: to-integer (screen-size/2) / (ctx-rebgui/sizes/cell)
table-size: as-pair cell-width (to-integer cell-height / 2.5)
current-margin: ctx-rebgui/sizes/margin
top-left: as-pair negate current-margin negate current-margin

display/maximize/close "POS" [
    at top-left #L main-menu: menu data [
        "File" [
            "     Print      " [print-receipt]
            "     Save       " [save-receipt]
            "     Load       " [load-receipt]
            "     Search     " [search-receipts]
        ]
        "Options" [
            "     Appearance     " [change-appearance]
        ] 
        "About" [
            "     Info     " [
                alert trim/lines {
                    Point of Sale System. 
                    Copyright  2010 Nick Antonaccio. 
                    All rights reserved.
                }
            ]
        ]
    ]
    return
    barcode: field #LW tip "Bar Code" [
        parts: parse/all copy barcode/text " "
        set-text f1 parts/1
        set-text f2 parts/2
        set-text f3 parts/3
        clear barcode/text
        add-new-item
    ]
    return
    f1: field tip "Item" 
    f2: field tip "Booth" 
    f3: field tip "Price (do NOT include '$' sign)" [
        add-new-item 
        set-focus add-button
    ]
    add-button: button -1 "Add Item" [
        add-new-item 
        set-focus add-button
    ]
    button -1 #OX "Delete Selected Item" [
        remove/part find pos-table/data pos-table/selected 3
        pos-table/redraw
        calculate-totals
    ]
    return
    pos-table: table (table-size) #LWH options [
        "Description" center .6
        "Booth" center .2
        "Price" center .2
    ] data []
    reverse
    panel sky #XY data [
        after 2
        text 20 "Subtotal:" subtotal-f: field 
        text 20 "     Tax:" tax-f: field
        text 20 "   TOTAL:" total-f: field
    ]
    reverse
    button -1 #XY "Lock" [do login]
    button -1 #XY "New" [clear-new]
    button -1 #XY "SAVE and PRINT" [save-receipt]
    do [set-focus barcode]
] [question "Really Close?"]

do-events

5.15 Creating PDF files using pdf-maker.r

The script at http://www.colellachiara.com/soft/Misc/pdf-maker.r generates PDF files directly from REBOL code. The official documentation is available at http://www.colellachiara.com/soft/Misc/pdf-maker-doc.pdf. No other software is required to create PDFs with REBOL.

To create PDF files, first import pdf-maker.r with the "do" function (or simply include it directly in your code). Next, run the "layout-pdf" function, which takes one block as a parameter, and write its output to a .pdf file using the "write/binary" function. Inside the block passed to the "layout-pdf" function, a variety of formatting functions can be included to layout text, images, and manually generated graphics. Here's a basic example of the format, with one simple text layout function:

do http://www.colellachiara.com/soft/Misc/pdf-maker.r
write/binary %example.pdf layout-pdf [[textbox ["Hello PDF world!"]]]

call %example.pdf

Here's a more complex example that creates a multi-page PDF file and demonstrates many of the basic capabilities of pdf-maker.r. Separate page content is contained in separate sub-blocks. All coordinates are written in MILLIMETER format:

REBOL [title: "pdf-maker example"]

do http://www.colellachiara.com/soft/Misc/pdf-maker.r

write/binary %example.pdf layout-pdf compose/deep [
    [
        page size 215.9 279.4  ; American Letter Size!!!
        textbox ["Here is page 1.  It just contains this text."]
    ] 
    [
        textbox 55 55 90 100 [
            {Here's page 2.  This text box contains a starting
             XxY position and an XxY size.  Coordinates are in
             millimeters and extend from the BOTTOM LEFT of the
             page (this box extends from starting point 55x55
             and is 90 mm wide, 100 mm tall).

             *** NOTE ABOUT PAGE SIZES - IMPORTANT!!! *** 

             All the following page sizes are the default ISO A4,
             or 211297 mm.  That is SLIGHTLY SMALLER than the
             standard American Letter page size.  If you are
             printing on American Letter sized paper, be sure to
             manually set your paper size, as is done on the first
             page of this example.}
        ]
    ]
    [
        textbox 0 200 200 50 [
            center font Helvetica 10.5
            {This is page 3.  The text inside this box is centered
             and formatted using Helvetica font, with a character
             size of 10.5 mm.}
        ]
    ]
    [
        apply rotation 20 translation 35 150 [
            textbox 4 4 200 20 [
                {This is page 4.  The textbox is rotated 20 degrees
                 and translated (moved over) 35x150 mm.  Graphics
                 and images can also be rotated and translated.}
            ]
        ]
    ]
    [
        textbox 5 200 200 40 [
            {Here's page 5.  It contains this textbox and several
             images.  The first image is placed at starting point
             50x150 and is 10mm wide by 2.4mm tall.  The second
             image is 2x bigger and rotated 90 degrees.  The last
             image is placed at starting point 100x150 and is
             streched to 10x its size.  Notice that this ENTIRE
             layout block has been evaluated with compose/deep to
             evaluate the images in the following parentheses.}
        ]
        image 50 150 10 2.4 (system/view/vid/image-stock/logo)
        image 50 100 20 4.8 rotated 90 
            (system/view/vid/image-stock/logo)
        image 100 150 100 24 (system/view/vid/image-stock/logo)
    ]
    [
        textbox [
            {This page contains this textbox and several generated
             graphics:  a line, a colored and filled box with a
             colored edge, and a circle.}
        ]
        line width 3  20 20 100 50   ; starting and ending XxY positions
        solid box edge width 0.2 edge 44.235.77 150.0.136 100 67 26 16
        circle 75 50 40   ; starting point 75x50, radius 40mm
    ]
]

call %example.pdf

The compose/deep evaluation is important when using computed values in PDF layouts. Take a look at the following example that uses computed coordinates and image values:

do http://www.colellachiara.com/soft/Misc/pdf-maker.r
xpos: 50  ypos: 200  offset: 5  
size: 5  width: (10 * size)  height: (2.4 * size)
page1: compose/deep [[
    image 
        (xpos + offset) (ypos + offset)
        (width) (height)
        (system/view/vid/image-stock/logo)
]]
write/binary %example.pdf layout-pdf page1
call %example.pdf

Here is a program that prints out guitar fretboard note diagrams that can be cut out, wrapped around, and taped directly to guitar fretboards of specific varied sizes.

REBOL [title:  "Guitar Fretboard Note Overlay Printer"]

chosen-scale: none
view center-face layout [
    h1 "Fretboard length:"
    text-list "25.5" "27.67" "30" [
        chosen-scale: join "scale" value
        unview
        alert rejoin [
            "Now printing " 
            value 
            " inch scale fretboard overlay to 'notes.pdf'"
        ]
    ]
]

notes: [
    [{F}{C}{ }{ }{ }{F}]
    [{ }{ }{A}{E}{B}{ }]
    [{G}{D}{ }{F}{C}{G}]
    [{ }{ }{B}{ }{ }{ }]
    [{A}{E}{C}{G}{D}{A}]
    [{ }{F}{ }{ }{ }{ }]
    [{B}{ }{D}{A}{E}{B}]
    [{C}{G}{ }{ }{F}{C}]
    [{ }{ }{E}{B}{ }{ }]
    [{D}{A}{F}{C}{G}{D}]
    [{ }{ }{ }{ }{ }{ }]
    [{E}{B}{G}{D}{A}{E}]
]

scale25.5: [
    36.35 70.66 103.05 133.62 162.47 189.71 215.41 239.67 262.58 284.19
    304.59 323.85 342.03 359.18 375.38 390.66 405.09 418.70 431.56 443.69
    455.14 465.95 476.15 485.77
]

scale27.67: [
    39.45 76.68 111.82 144.99 176.30 205.85 233.74 260.07 284.92 308.38
    330.51 351.41 371.13 389.75 407.32 423.91 439.56 454.34 468.28 481.45
    493.87 505.60 516.67 527.11
]

scale30: [
    42.77 83.14 121.24 157.20 191.15 223.18 253.43 281.97 308.91 334.34
    358.34 381.00 402.38 422.57 441.62 459.60 476.57 492.59 507.71 521.99
    535.46 548.17 560.17 571.50
]

x: 40  line-width: 30  text-width: 4  height: 5

make-overlay: does [
    page1: copy [
        textbox 40 0 4 6 [center font Helvetica 5 "E"]
        textbox 45 0 4 6 [center font Helvetica 5 "B"]
        textbox 50 0 4 6 [center font Helvetica 5 "G"]
        textbox 55 0 4 6 [center font Helvetica 5 "E"]
        textbox 60 0 4 6 [center font Helvetica 5 "A"]
        textbox 65 0 4 6 [center font Helvetica 5 "E"]
    ]
    output: copy []
    for i 1 10 1 [
        y: do compose [pick (to-word chosen-scale) i]
        notes-at-fret: pick notes i
        append page1 compose/deep [
            line width 4 (x) (y) (x + line-width) (y)
            textbox (x) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/1)
            ]
            textbox (x + 5) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/2)
            ]
            textbox (x + 10) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/3)
            ]
            textbox (x + 15) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/4)
            ]
            textbox (x + 20) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/5)
            ]
            textbox (x + 25) (y - 10) (text-width) (height + 1) [
                center font Helvetica (height) (notes-at-fret/6)
            ]
        ]
    ]
    append/only output page1
    write/binary %notes.pdf layout-pdf output
    alert "Done"
]

do http://www.colellachiara.com/soft/Misc/pdf-maker.r
make-overlay

5.16 Creating .swf Files with REBOL/Flash

Multimedia .swf ("flash") files can be created entirely with REBOL, using the REBOL/flash script at http://box.lebeda.ws/~hmm/rswf/rswf_latest.r. The following 3 lines demonstrate the basic process of installing the dialect (DOing the REBOL/flash script file), compiling a downloaded REBOL/flash source code file, and then viewing the compiled .swf file in your browser:

do http://box.lebeda.ws/~hmm/rswf/rswf_latest.r   ; install REBOL/flash
make-swf/save/html http://tinyurl.com/yhex2cf     ; compile the source
browse %starfield1.html                           ; view the created .swf

To begin working with REBOL/flash in earnest, you'll want to save a copy of the REBOL/flash dialect to your hard drive:

write %rswf.r read http://box.lebeda.ws/~hmm/rswf/rswf_latest.r

Here are a few demo examples to download:

examples: [
    http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-eyeball.rswf
    http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-snow.rswf
    http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-starfield2.rswf
]
foreach file examples [write (to-file last split-path file) (read file)]

Those are just several of the 175 code examples at http://box.lebeda.ws/~hmm/rswf/. That large collection of code examples represents the full existing documentation for the REBOL/flash dialect. After downloading/doing the script at http://box.lebeda.ws/~hmm/rswf/rswf_latest.r, you can compile REBOL/flash source code files into working .swf files using the "make-swf" function. The /save and /html refinements of that function are most typically used, as they generate the HTML needed to insert the Flash object in your own web pages. The make-swf/save/html function creates a fully functional .swf Flash file and the necessary container HTML file in the same folder as rswf.r. Here's a simple script to compile REBOL/flash source files, and then view the compiled results in the browser:

REBOL [title: "Compile and View Flash Files"]

my-rswf-folder: %./
; my-rswf-folder: %/C/12-2-09/My_Docs/WORK/rswf/  ; choose your own
change-dir my-rswf-folder
do %rswf.r   ; assuming you've already saved it to your hard drive
make-swf/save/html to-file request-file/filter "*.rswf"
browse to-file request-file/filter "*.html"

This next script is a build tool that helps automate the process of creating, editing, and compiling REBOL/flash source files, viewing the results, and then re-doing the entire process repeately to debug and complete projects quickly:

REBOL [title: "REBOL/flash Build Tool"]

; The following folder should be set to where you keep your REBOL/flash
; project files: 

my-rswf-folder: %./
; my-rswf-folder: %/C/12-2-09/My_Docs/WORK/rswf/
change-dir my-rswf-folder
do %rswf.r
current-source: to-file request-file/filter/file "*.rswf" %test.rswf
unset 'output-html

do edit-compile-run: [
    editor current-source
    if error? err: try [make-swf/save/html current-source] [
        err: disarm :err
        alert reform [
            "The following compile error occurred: "
            err/id err/where err/arg1
        ]
        either true = request "Edit/Compile/Run Again?" [
            do edit-compile-run quit
        ] [
            quit
        ]
    ]
    unless value? 'output-html [
        output-html: to-file request-file/filter "*.html"
    ]
    browse output-html
    if true = request "Edit/Compile/Run Again?" [do edit-compile-run]
]

Try this example, derived from http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-soundstream.rswf:

write %mp3.rswf {
    REBOL [
        type: 'swf5
        file: %mp3.swf
        background: 200.200.200
        rate: 12
        size: 1x1
    ]
    mp3Stream http://re-bol.com/example.mp3
    finish stream
    end
}

Notice that the original file name, the .mp3 file link, the graphic size and background color, and the header have been adjusted. The script below is another build tool that can be used to create .swf files, and then upload them to a web server:

REBOL [title: "Generate from source and upload SWF and HTML to web site"]

; You can edit these file names and FTP info for your own use:

source-file: %mp3.rswf
output-html: %mp3.html
output-swf:  %mp3.swf
inserted-html: {<center><h1>MP3 Example!</h1></center>}
insert-at: {<BODY bgcolor="#C8C8C8">}
my-ftp-info: ftp://username:password@site.com/public_html/folder/
destination-url: http://re-bol.com/examples/mp3.html
do http://box.lebeda.ws/~hmm/rswf/rswf_latest.r  ; do %rswf.r
make-swf/save/html source-file 
content: read output-html
insert (skip find content insert-at length? insert-at) inserted-html
write output-html content
write (join my-ftp-info form output-html) (read output-html)
write/binary (join my-ftp-info output-swf) (read/binary output-swf)
browse destination-url

Now take a look at http://box.lebeda.ws/~hmm/rswf/example/swf5-3dtext. Notice that the source code for this example (http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-3dtext.rswf) imports some assets from a separate included file, stored on the web server (at %includes/fnt_euromode_b.swf). Files such as images and other binary resources are often included in REBOL/flash code, so they can be compiled into the final .swf file. If you want to compile this source code on your local machine, you must download the included files, as well as the source code:

; Notice the folder which contains the included resource.  By
; saving downloaded resources to the same folder structure, you
; don't need to alter the original code at all:

make-dir %./includes/
web-resource: http://box.lebeda.ws/~hmm/rswf/includes/fnt_euromode_b.swf
local-resource: %./includes/fnt_euromode_b.swf
write/binary local-resource read/binary web-resource

source: http://box.lebeda.ws/~hmm/rswf/examples/swf5/swf5-3dtext.rswf
do %rswf.r
make-swf/save/html source

; Notice the output file name in the header of the source code file:

browse %3dtext.html

The code example above is complex, but you should notice that much of it is in standard REBOL format (colons are used to create variables, code is contained in square bracketed blocks, functions and structures such as the "for" loop are formatted in standard REBOL syntax, etc.). You'll see many words and phrases in this example that are used in other examples: ImportAssets, EditText, sprite, place, at, "show 2 frames", doAction, "goto 1 and play", showFrame, end, etc. To understand how to use the dialect, start with simpler examples and look for the common words, study their syntax, and experiment with adjusting their parameters. Below are a few examples which demonstrate the basics of using text, images, graphics, sounds, and animation techniques in REBOL/flash. First, a rectangle:

REBOL [
    type: 'swf
    file: %shape.swf
    background: 230.230.230
    rate: 40
    size: 320x240
]
a-rectangle: Shape [
    Bounds 0x0 110x50
    fill-style [color 255.0.0]
    box 0x0 110x50
]
place [a-rectangle] at 105x100
showFrame
end

Here's some text:

REBOL [
    type: 'swf
    file: %text.swf
    background: 255.255.255
    rate: 40
    size: 320x240
]
fnt_Arial: defineFont2 [name "Arial"]
some-text: EditText 'the-text 110x18 [
    Color 0.0.0
    ReadOnly
    NoSelect
    Font [fnt_Arial 12]
    Layout [align: 'center]
]
place [some-text] at 105x100
doAction [the-text: "Hello world!"]
showFrame
end

Try altering all the above examples (copy/paste and edit them directly with the "REBOL/flash Build Tool" provided earlier). They should provide enough basic understanding to begin reading through the API examples at http://box.lebeda.ws/~hmm/rswf/.

5.17 Rebcode

To improve low level code speed see http://www.rebol.com/docs/rebcode.html and http://www.rebol.net/rebcode/. To use Rebcode, you must use a version of the REBOL interpreter with the Rebcode VM enabled (for Windows, search rebview1361031.exe). Here's an example program using Rebcode:

do http://www.rebol.org/view-script.r?script=galaga.r

5.18 Useful REBOL Tools

Here are some web links containing free code modules and various programs that can help you accomplish useful programmatic tasks in REBOL:

http://www.hmkdesign.dk/rebol/list-view/list-view.r - a powerful listview widget to display and manipulate formatted data in GUI applications.

http://www.dobeash.com/rebdb.html - a database module written entirely in native REBOL code that lets you easily store and organize data. This is the same web site where you'll find the REBOL sqlite module and RebGUI (covered earlier): http://www.dobeash.com/rebgui.html

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=rebzip.r - a module to compress/decompress zip formatted files.

http://www.colellachiara.com/soft/Misc/pdf-maker.r - a dialect to create pdf files directly in REBOL (covered earlier).

http://softinnov.org/rebol/mysql.shtml - a module to directly manipulate mysql databases within REBOL (covered earlier). A module for postgre databases and a captcha program are also freely available at the same site.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=menu-system.r - a dialect to create all types of useful GUI menus in REBOL (covered earlier).

http://softinnov.org/rebol/uniserve.shtml - a framework to help build client-server network applications.

http://softinnov.org/cheyenne.shtml - a full featured web server written entirely in native REBOL. It enables inline, PHP-like server scripting.

http://www.rebol.net/demos/BF02D682713522AA/i-rebot.r http://www.rebol.net/demos/BF02D682713522AA/objective.r and http://www.rebol.net/demos/BF02D682713522AA/histogram.r - these examples contain a 3D engine module written entirely in native REBOL draw dialect. The module lets you easily add and manipulate 3D graphics objects in your REBOL apps (covered earlier).

http://web.archive.org/web/20030411094732/www3.sympatico.ca/gavin.mckenzie/ - a REBOL XML parser library.

http://earl.strain.at/space/rebXR - a full client/server XML-RPC implementation for REBOL (contains the parser library above). Tutorials (translated from French by Google) are available here and here.

http://box.lebeda.ws/~hmm/rswf/ - a dialect to create flash (SWF) files directly from REBOL scripts (covered earlier).

libwmp3.dll - the easiest way to control full featured mp3 playback in REBOL. http://www.rebol.org/view-script.r?script=mp3-player-libwmp.r demonstrates how to use it in REBOL.

http://www.rebolforces.com/articles/tui-dialect/ - a dialect to position characters on the screen in command line versions of REBOL.

http://www.rebol.net/docs/makedoc.html - converts text files into nicely formatted HTML files. This tutorial page is written and maintained entirely with makedoc (you can see the makedoc source at http://re-bol.com/rebol.txt).

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=layout-1.8.r - a simple visual layout designer for REBOL GUI code. NOT stable enough for commercial use, but helpful for quickly laying out simple GUI designs.

http://www.crimsoneditor.com/ - a source code editor for Windows, with color highlighting especially for REBOL syntax. Quick start instructions are available at http://www.rebol.net/article/0187.html.

http://www.rebol.org - the official REBOL library - full of many additional modules and useful code fragments. The first place to look when searching for REBOL source code.

5.19 6 REBOL Flavors

This tutorial covers a version of the REBOL language interpreter called REBOL/View. REBOL/View is actually only one of several available REBOL releases. Here's a quick description of the different versions:

  1. View - free to download and use, it includes language constructs used to create and manipulate graphic elements. View comes with the built-in dialect called "VID", which is REBOL's shorthand mini-language used to display common GUI widgets. View and VID dialect concepts have been integrated throughout this document. The "layout" word in a typical "view layout" GUI design signifies the use of VID dialect code in the enclosed block. The VID dialect is used internally by the REBOL interpreter to parse and convert simple VID code to lower level View commands, which are composed from scratch by the rudimentary display engine in REBOL. VID makes GUI creation simple, without the need to deal with graphics at a rudimentary level. But for fine control of all graphic operations, the full View language is exposed in REBOL/View, and can be mixed with VID code. View also has a built-in "draw" dialect that's used to compose and alter images on screen. Aside from graphic effects, View has built in sound, and access to the "call" function for executing command line applications. As of version 2.76, REBOL/View contains many capabilities that were previously only available in commercial versions (dll, database, encryption, SSL, and more - see below). The newest official releases of View can be download from http://rebol.com/view-platforms.html. The newest test versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-view.html.
  2. Core - a text-only version of the language that provides basic functionality. It's smaller than View (about 1/3 to 1/2 the file size), without the GUI extensions, but still fully network enabled and able to run all non-graphic REBOL code constructs. It's intended for console and server applications, such as CGI scripting, in which the GUI facilities are not needed. Core is also free and can be downloaded from http://rebol.com/platforms.html. Newest versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-core.html.
  3. View/Pro - created for professional developers, it adds encryption features, Dll access and more. Pro licenses are not free. See http://www.rebol.com/purchase.html. NOTE: STARTING IN VERSION 2.76, THESE FEATURES ARE AVAILABLE IN THE FREELY DOWNLOADABLE VERSIONS OF REBOL!
  4. SDK - also intended for professionals, it adds the ability create stand-alone executables from REBOL scripts, as well as Windows registry access and more to View/Pro. SDK licenses are not free.
  5. Command - another commercial solution, it adds native access to common database systems, SSL, FastCGI and other features to View/Pro. NOTE: STARTING IN VERSION 2.77, THESE FEATURES ARE AVAILABLE IN THE FREELY DOWNLOADABLE VERSIONS OF REBOL!
  6. Command/SDK - combines features of SDK and Command.

Some of the functionalities provided by SDK and Command versions of REBOL have been enabled by modules, patches, and applications created by the REBOL user community. For example, mysql and postgre database access, dll access, and stand-alone executable packaging can be managed by free third party creations (search rebol.org for options). Because those solutions don't conform to official REBOL standards, and because no support for them is offered by REBOL Technologies, commercial solutions by RT are recommended for critical work.

5.20 Bindology, Dialects, Metaprogramming and Other Advanced Topics

If your interests run deeper than simple scripting, be sure to read http://blog.revolucent.net/search/label/REBOL. The bindology article at http://www.rebol.net/wiki/Bindology and other pages at http://www.fm.vslib.cz/~ladislav/rebol provide more understanding.

5.20.1 Metaprogramming

Many REBOLers tend to use metaprogramming techniques intuitively as a result of the design of the language. A primary concept in REBOL is that data is code, and code is data. You can easily create blocks and strings that can be executed simply with the "do" function:

REBOL []

function: ask "'print' or 'editor':  "
text: ask "Enter some text:  "
do compose [(to-word function) (text)]
halt

The task above could also be completed by "DO"ing a concatenated string:

do rejoin [function { "} text {"}]

Understanding "reduce", "compose", and related functions are key to the above concept, along with understanding how to build and manipulate strings and blocks which can be executed. Search this tutorial for examples that contain "compose", "reduce", and "rejoin" functions, and you'll see many simple but useful "do compose [some (generated) code]" and "do rejoin [{some } generated { } code]" examples. Also, look at the "Voice Alarms" and "VOIP" scripts, as well as the final webcam program in the "multitasking" section. Those examples run separate .r scripts using the "launch" function, which have been created dynamically by a main script. Similarly, it's common to use the "browse" function to view dynamically created HTML, as in the "Guitar Chord Diagram Maker", "Point of Sale System", "Guitar Chords", and other examples in this text. This technique is often used to create printable documents in REBOL.

Another common technique is to write code which is capable of building dynamically changeable GUI layout blocks. Here's a simple example:

view center-face layout [
    text "Select a button width, in pixels:"
    d: drop-down data [250 400 550]
    text "Enter any number of button labels (text separated by spaces):"
    f: field 475
    btn "Generate GUI" [
        created-buttons: copy compose [
            style new-btn btn (to-integer d/text) [
                alert join "This button's label is: " face/text
            ]
        ]
        foreach item to-block f/text [
            append created-buttons compose [
                new-btn (form item)
            ]
        ]
        view/new center-face layout created-buttons
    ]
]

The entire VID "layout" function is actually a metaprogramming tool. To use VID, you compose blocks, which are evaluated by the layout function, and the generated native REBOL/view code is then run by REBOL's lower level compositing engine. RebGUI works the same way. In this manner, many REBOL dialects are metaprogramming tools. Dialects ("DSL"s) tend to rely heavily on the use of the "parse" function to (re)organize the meaning of input given in a specified syntax. There's a short introductory article which might be helpful in relation to this topic at http://computer-programming-languages.suite101.com/article.cfm/how_to_createa_rebol_dialect . REBOL/flash and pdf-maker.r are powerful examples of REBOL metaprogramming tools that are productive outside the realm of REBOL coding. Both those tools use dialects as input, and they actually output interpreted formats (REBOL/flash creates SWF files, and pdf-maker creates PDF files). If you want to know more about metaprogramming with dialects, study parse, contexts, and bind, and examine the various dialect tools created by the REBOL community.

6. A Generalized Approach to Writing Software:

  1. Start with a detailed definition of what the application should do, in human terms. You won't get anywhere in the design process until you can describe some form of imagined final program. Write down your explanation and flesh out the details of the imaginary program as much as possible. Include as much detail as possible: what should the program look like, how will the user interact with it, what sort of data will it take in, process, and return, etc.
  2. Determine a list of general code and data structures related to each of the 'human-described' program goals above. Take stock of any general code patterns which relate to the operation of each imagined program component. Think about how the user will get data into and out of the program. Will the user work with a desktop GUI window, web forms that connect to a CGI script, or directly via command line interactions in the interpreter console? Consider how the data used in the program can be represented in code, organized, and manipulated. What types of data will be involved (text types such as strings, time values, or URLs, binary types such as images and sounds, etc.). Could the program code potentially make use of variables, block/series structures and functions, or object structures? Will the data be stored in local files, in a remote database, or just in temporary memory? Think of how the program will flow from one operation to another. How will pieces of data need to be sorted, grouped and related to one another, what types of conditional and looping operations need to be performed, what types of repeated functions need to be isolated and codified? Consider everything which is intended to happen in the imagined piece of software, and start thinking, "_this_ is how I could potentially accomplish _that_, in code...".
  3. Begin writing a code OUTLINE. It's often easiest to do this by outlining a user interface, but a flow chart of operations can be helpful too. The idea here is to begin writing a generalized code container for your working program. At this point, the outline can be filled with simple natural language PSEUDO CODE that DESCRIBES how actual code can be organized. Starting with a user interface outline is especially helpful because it provides a starting point to actually write large code structures, and it forces you to deal with how the program will handle the input, manipulation, and output of data. Simple structures such as "view layout [button [which does this when clicked...]]", "block: [with labels and sub-blocks organized like this...], "function: (which loops through this block and saves these elements to another variable...)" can be fleshed out later with complete code.
  4. Finally, move on to replacing pseudo code with actual working code. This isn't nearly as hard once you've completed the previous steps. A language dictionary/guide with cross referenced functions is very helpful at this stage. And once you're really familiar with all the available constructs in the language, all you'll likely need is an occasional syntax reminder from REBOL's built-in help. Eventually, you'll pass through the other design stages much more intuitively, and get to/through this stage very quickly.
  5. As a last step, debug your working code and add/change functionality as you test and use the program.

The basic plan of attack is to always explain to yourself what the intended program should do, in human terms, and then think through how all required code structures must be organized to accomplish that goal. As an imagined program takes shape, organize your work flow using a top down approach: imagined concept -> general outline -> pseudo code description / thought process -> working code -> finished code.

The majority of code you write will flow from one user input, data definition or internal function to the next. Begin mapping out all the things that need to "happen" in the program, and the info that needs to be manipulated along the way, in order for those things to happen, from beginning to end. The process of writing an outline can be helped by thinking of how the program must begin, and what must be done before the user starts to interact with the application. Think of any data or actions that need to be defined before the program starts. Then think of what must happen to accommodate each possible interaction the user might choose. In some cases, for example, all possible actions may occur as a result of the user clicking various GUI widgets. That should elicit the thought of certain bits of GUI code structure, and you can begin writing an outline to design a GUI interface. If you imagine an online CGI application, the user will likely work with forms on a web page. You can begin to design HTML forms, which will inevitably lead to specifying the variables that are passed to the CGI app. If your program will run as a simple command line app, the user may respond to text questions. Again, some code from the example applications in this tutorial should come to mind, and you can begin to form a coding structure that enables the general user interface and work flow.

Sometimes it's simpler to begin thinking through a development process using console interactions. It tends to be easier to develop a CGI application if you've got working console versions of various parts of the program. Whatever your conceived interface, think of all the choices the user can make at any given time, and provide a user interface component to allow for those choices. Then think of all the operations the computer must perform to react to each user choice, and describe what must happen in the code.

As you tackle each line of code, use natural language pseudo code to organize your thoughts. For example, if you imagine a button in a GUI interface doing something for your user, you don't need to immediately write the REBOL code that the button runs. Initially, just write a description of what you want the button to do. The same is true for functions and other chunks of code. As you flesh out your outline, describe the language elements and coding thought you conceive to perform various actions or to represent various data structures. The point of writing pseudo code is to keep clearly focused on the overall design of the program, at every stage of the development process. Doing that helps you to avoid getting lost in the nitty gritty syntax details of actual code. It's easy to lose sight of the big picture whenever you get involved in writing each line of code.

As you convert you pseudo code thoughts to language syntax, remember that most actions in a program occur as a result of conditional evaluations (if this happens, do this...), loops, or linear flow from one action to the next. If you're going to perform certain actions multiple times or cycle through lists of data, you'll likely need to run through some loops. If you need to work with changeable data, you'll need to define some variable words, and you'll probably need to pass them to functions to process the data. Think in those general terms first. Create a list of data and functions that are required, and put them into an order that makes the program structure build and flow from one definition, condition, loop, GUI element, action, etc., to the next.

What follows are a number of real world examples created using the above process. Case studies that trace my train of thought from the organizational process through the completed code for each of these examples are availabe at http://re-bol.com

7. REAL WORLD EXAMPLES

7.1 Case 1 - Online Schedule Text Editor

; first set I some initial required variables:

url: http://website.com/user
ftp-url: ftp://user:pass@website.com/public_html/user

; ... and gave the user some instructions: 

alert {Edit your schedule, then click save and quit.
    The website will be automatically updated.}

; 1) download the file containing the schedule text:

write %schedule.txt read rejoin [url "/schedule.txt"]

; 2) create a timestamped backup on the web server:

write rejoin [ftp-url "/" now/date "_" now/time ".txt"] read %schedule.txt

; 3 and 7) edit the text:

editor %schedule.txt

; 4) save the edited text back to the web site:

write rejoin [ftp-url "/schedule.txt"] read %schedule.txt

; 6) confirm that the changes are displayed correctly:

browse url

In the http://website.com/user folder on the web site, I created an index.cgi script containing the following code to properly display the schedule text, and to provide a link to download the program above (packaged as an executable using XpackerX):

#!/home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"

print {<a href="./scheduler.exe" target=_blank>Download Scheduler</a><br>}
print rejoin ["<pre>" read %schedule.txt "</pre>"]

7.2 Case 2 - A Simple Image Gallery CGI Program

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "Photo Viewer"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Jam Session Photos"</TITLE></HEAD><BODY>]
print read %pageheader.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 %pagefooter.html

7.3 Case 3 - Days Between Two Dates Calculator

Simplest version:

REBOL []

sd: request-date   ; get the START-DATE
ed: request-date   ; get the END-DATE
db: ed - sd        ; calculate the DAYS-BETWEEN
alert rejoin ["Days between " sd " and " ed ": " db]  ; display the result

Final version:

REBOL [title: "Days Between"]

sd: ed: now/date    
view layout [
    btn "Select Start Date" [
        sd: request-date 
        sdt/text: to-string sd
        show sdt 
        db/text: to-string ((to-date edt/text) - sd)
        show db
    ]
    sdt: field to-string sd [
        either error? try [to-date sdt/text] [
            alert "Improper date format."
        ] [
            db/text: to-string ((to-date edt/text) - (to-date sdt/text))
            show db
        ]
    ]
    btn "Select End Date" [
        ed: request-date
        edt/text: to-string ed 
        show edt 
        db/text: to-string (ed - (to-date sdt/text)) 
        show db
    ]
    edt: field to-string ed [
        either error? try [to-date edt/text] [
            alert "Improper date format."
        ] [
            db/text: to-string ((to-date edt/text) - (to-date sdt/text))
            show db
        ]
    ]
    h1 "Days Between:"
    db: field "0" [
        either error? try [to-integer db/text] [
            alert "Please enter a number."
        ] [
            edt/text: to-string (
                (to-date sdt/text) + (to-integer db/text)
            )
        ]
        show edt
    ]
]

7.4 Case 4 - Simple Search

Local file system version:

REBOL [title: "Simple Search"]

phrase: request-text/title/default "Text to Find:" "the"
start-folder: request-dir/title "Folder to Start In:"
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 [{"} phrase {" found in:  } what-dir item]
                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 [{SEARCHING for "} phrase {" in } start-folder "...^/"]
recurse %.\
print "^/DONE^/"
editor found-list
halt

CGI version:

#! /home/yourpath/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Search"</TITLE></HEAD><BODY>]
; print read %template_header.html

submitted: decode-cgi system/options/cgi/query-string

if not empty? submitted [
    phrase: submitted/2
    start-folder: to-file submitted/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 [{"} phrase {" found in:  }
                        what-dir item {<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 [{SEARCHING for "} phrase {" in } 
        start-folder {<BR><BR>}]
    recurse %.\
    print "<BR>DONE <BR>"
    ; save %found.txt found-list
    ; print read %template_footer.html
    quit
]

print [<CENTER><TABLE><TR><TD>]
print [<FORM ACTION="./search.cgi">]
print ["Text to search for:" <BR> 
    <INPUT TYPE="TEXT" NAME="phrase"><BR><BR>]
print ["Folder to search in:" <BR> 
    <INPUT TYPE="TEXT" NAME="folder" VALUE="../yourfolder/" ><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</TD></TR></TABLE></CENTER>]
; print read %template_footer.html

7.5 Case 5 - A Simple Calculator Application

REBOL [title: "Calculator"]

prev-val: cur-val: 0 cur-eval: "+" display-flag: false
print "0"
view center-face layout/tight [
    size 300x350 space 0x0 across
    display: field 300x50 font-size 28 "0" return
    style butn button 100x50  [
        if display-flag = true [display/text: "" display-flag: false]
        if display/text = "0" [display/text: ""]
        display/text: rejoin [display/text value] 
        show display
        cur-val: display/text
    ]
    style eval button 100x50 brown font-size 13 [
        prev-val: cur-val
        display/text: "" show display
        cur-eval: value
    ]
    butn "1"  butn "2"  butn "3"  return
    butn "4"  butn "5"  butn "6"  return
    butn "7"  butn "8"  butn "9"  return 
    butn "0"  butn "."  eval "+" return
    eval "-" eval "*" eval "/" return
    button 300x50 gray font-size 16 "=" [
        if display-flag <> true [ 
            if ((cur-eval = "/") and (cur-val = "0")) [
                alert "Division by 0 is not allowed." break
            ]
            prin rejoin [prev-val " " cur-eval " " cur-val " = "]
            print display/text: cur-val: do rejoin [
                prev-val " " cur-eval " " cur-val
            ]
            show display
            display-flag: true
        ]
    ]
]

7.6 Case 6 - A Backup Music Generator (Chord Accompaniment Player)

REBOL [title: "Chord Accompaniment Player"]

play: false
insert-event-func [
    either event/type = 'close [
        if play = true [play: false close sound-port]
        really: request "Really close the program?"
        if really = true [quit]
    ][
        event
    ]
]

unless exists? %chord_data.r [
    write %chord_data.r read http://re-bol.com/chord_data.r
    do %chord_data.r
]

view center-face layout [
    across
    h2 "Chords:"
    tab
    chords: area 392x300 trim {
        bm bm bm bm
        gb7 gb7 gb7 gb7
        a a a a 
        e e e e
        g g g g
        d d d d 
        em em em em
        gb7 gb7 gb7 gb7
        g g g g
        d d d d
        gb7 gb7 gb7 gb7
        bm bm bm bm
        g g g g
        d d d d
        em em em em
        gb7 gb7 gb7 gb7
    }
    return
    h2 "Delay:"
    tab
    tempo: field 50 "0.35" text "(seconds)"
    tabs 40 tab
    btn "PLAY" [
        play: true
        the-tempo: to-decimal tempo/text
        sounds: to-block chords/text
        wait 0
        sound-port: open sound://
        forever [
            foreach sound sounds [
                if play = false [break]
                do rejoin ["insert sound-port " reduce [sound]]
                wait sound-port
                wait the-tempo 
            ]
            if play = false [break]
        ]
    ]
    btn "STOP" [
        play: false
        close sound-port
    ]
    btn "Save" [save to-file request-file/save chords/text]
    btn "Load" [chords/text: load read to-file request-file show chords]
    btn "HELP" [
        alert {
            This program plays chord progressions.  Simply type in
            the names of the chords that you'd like played, with a
            space between each chord.  For silence, use the
            underscore ("_") character.  Set the tempo by entering a 
            delay time (in fractions of second) to be paused between
            each chord.  Click the start button to play from the 
            beginning, and the stop button to end.  Pressing start
            again always begins at the first chord in the 
            progression.  The save and load buttons allow you to 
            store to the hard drive any songs you've created.
            Chord types allowed are major triad (no chord symbol - 
            just a root note), minor triad ("m"), dominant 7th 
            ("7"), major 7th ("maj7"), minor 7th ("m7"), diminished
            7th ("dim7"), and half diminished 7th ("m7b5").
            *** ALL ROOT NOTES ARE LABELED WITH FLATS (NO SHARPS)
            F# = Gb, C# = Db, etc...
         }
    ]
]

Here are a few chord examples to load. All the chords:

a bb b c db d eb e f gb g ab 
am bbm bm cm dbm dm ebm em fm gbm gm abm 
a7 bb7 b7 c7 db7 d7 eb7 e7 f7 gb7 g7 ab7 
adim7 bbdim7 bdim7 cdim7 dbdim7 ddim7 
ebdim7 edim7 fdim7 gbdim7 gdim7 abdim7
am7b5 bbm7b5 bm7b5 cm7b5 dbm7b5 dm7b5 
ebm7b5 em7b5 fm7b5 gbm7b5 gm7b5 abm7b5 
am7 bbm7 bm7 cm7 dbm7 dm7 ebm7 em7 fm7 gbm7 gm7 abm7
amaj7 bbmaj7 bmaj7 cmaj7 dbmaj7 dmaj7
ebmaj7 emaj7 fmaj7 gbmaj7 gmaj7 abmaj7
_ _ _ _

Brown Eyed Girl:

g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
c c d d  g g em em c c d d

7.7 Case 7 - FTP Tool

REBOL [title: "FTP Tool"]

Instructions: {

    Enter your username, password, and FTP URL in the text field, and
    hit [ENTER].

    BE SURE TO END YOUR FTP URL PATH WITH "/".  

    URLs can be saved and loaded in multiple config files for future use.

    CONFIG FILES ARE STORED AS PLAIN TEXT, SO KEEP THEM SECURE.

    Click folders to browse through any dir on your web server.  Click
    text files to open, edit and save changes back to the server.
    Click images to view.  Also upload/download any type of file,
    create new files and folders, change file names, copy and delete
    files, change permissions, etc.

}
connect: does [
    either (to-string last p/text) = "/" [
    if error? try [
            f/data: sort append read to-url p/text "../" show f
        ][
            alert "Not a valid FTP address, or the connection failed."
        ]
    ][
        editor to-url p/text
    ]
]
view center-face layout [
    p: field 600 "ftp://user:pass@website.com/" [connect]
    across
    btn "Connect" [connect]
    btn "Load URL" [
        config: to-file request-file/file %/c/ftp.cfg
        either exists? config [
            if (config <> %none) [
                my-urls: copy []
                foreach item read/lines config [append my-urls item]
                if error? try [
                    p/text: copy request-list "Select a URL:" my-urls
                ] [break]
            ]
        ][
            alert "First, save some URLs to that file..."
        ]
        show p focus p
    ]
    btn "Save URL" [
        url: request-text/title/default "URL to save:" p/text
        if url = none [break]
        config-file: to-file request-file/file/save %/c/ftp.cfg
        if (url <> none) and (config-file <> %none) [
            if not exists? config-file [
                write/lines config-file ftp://user:pass@website.com/
            ]
            write/append/lines config-file to-url url
            alert "Saved"
        ]
    ]
    below
    f: text-list 600x350 [
        either (to-string value) = "../" [
            for i ((length? p/text) - 1) 1 -1 [
                if (to-string (pick p/text i)) = "/" [
                    clear at p/text (i + 1) show p
                    f/data: sort append read to-url p/text "../" show f
                    break
                ]
            ]
        ][
            either (to-string last value) = "/" [
                p/text: rejoin [p/text value] show p
                f/data: sort append read to-url p/text "../" show f
            ][
                if ((request "Edit/view this file?") = true) [
                    either find [%.jpg %.png %.gif %.bmp] suffix? value [
                        view/new layout [
                            image load to-url join p/text value
                        ]
                    ][
                        editor to-url rejoin [p/text value]
                    ]
                ]
            ]
        ]
    ]
    across
    btn "Get Info" [
        p-file: to-url rejoin [p/text f/picked]
        alert rejoin ["Size: " size? p-file " Date: " modified? p-file]
    ]
    btn "Delete" [
        p-file: to-url request-text/title/default "File to delete:"
            join p/text f/picked
        if ((confirm: request "Are you sure?") = true) [delete p-file]
        f/data: sort append read to-url p/text "../" show f
        if confirm = true [alert "File deleted"]
    ]
    btn "Rename" [
        new-name: to-file request-text/title/default "New File Name:"
            to-string f/picked
        if ((confirm: request "Are you sure?") = true) [
            rename (to-url join p/text f/picked) new-name
        ]
        f/data: sort append read to-url p/text "../" show f
        if confirm = true [alert "File renamed"]
    ]
    btn "Copy" [
        new-name: to-url request-text/title/default "New Path:"
            (join p/text f/picked)
        if ((confirm: request "Are you sure?") = true) [
            write/binary new-name read/binary to-url join p/text f/picked
        ]
        f/data: sort append read to-url p/text "../" show f
        if confirm = true [alert "File copied"]
    ]
    btn "New File" [
        p-file: to-url request-text/title/default "New File Name:"
            join p/text "ENTER-A-FILENAME.EXT"
        if ((confirm: request "Are you sure?") = true) [
            write p-file ""
            ; editor p-file
        ]
        f/data: sort append read to-url p/text "../" show f
        if confirm = true [alert "Empty file created - click to edit."]
    ]
    btn "New Dir" [
        make-dir x: to-url request-text/title/default "New folder:" p/text
        alert "Folder created"
        p/text: x show p
        f/data: sort append read to-url p/text "../" show f
    ]
    btn "Download" [
        file: request-text/title/default "File:" (join p/text f/picked)
        l-file: next to-string (find/last (to-string file) "/")
        save-as: request-text/title/default "Save as..." to-string l-file
        write/binary (to-file save-as) (read/binary to-url file)
        alert "Download Complete"
    ]
    btn "Upload" [
        file: to-file request-file
        r-file: request-text/title/default "Save as..." 
            join p/text (to-string to-relative-file file)
        write/binary (to-url r-file) (read/binary file)
        f/data: sort append read to-url p/text "../" show f
        alert "Upload Complete"
    ]
    btn "Chmod" [
        p-file: to-url request-text/default rejoin [p/text f/picked]
        chmod: to-block request-text/title/default "Permissions:"
            "read write execute"
        write/binary/allow p-file (read/binary p-file) chmod
        alert "Permissions changed"
    ]
    btn-help [inform layout[backcolor white text bold as-is instructions]]
    do [focus p]
]

7.8 Case 8 - Jeopardy Trainer

REBOL [title: "Jeopardy"] 

config: {

    REBOL []

    ;________________________________________________________________

    sizer: 4

    Category-1: "Category 1" 
    Category-2: "Category 2" 
    Category-3: "Category 3" 
    Category-4: "Category 4" 
    Category-5: "Category 5"

    answers: [
        "$100 Answer, Category 1" 
        "$100 Answer, Category 2" 
        "$100 Answer, Category 3" 
        "$100 Answer, Category 4" 
        "$100 Answer, Category 5" 
        "$200 Answer, Category 1" 
        "$200 Answer, Category 2" 
        "$200 Answer, Category 3" 
        "$200 Answer, Category 4" 
        "$200 Answer, Category 5" 
        "$300 Answer, Category 1" 
        "$300 Answer, Category 2" 
        "$300 Answer, Category 3" 
        "$300 Answer, Category 4" 
        "$300 Answer, Category 5" 
        "$400 Answer, Category 1" 
        "$400 Answer, Category 2" 
        "$400 Answer, Category 3" 
        "$400 Answer, Category 4" 
        "$400 Answer, Category 5" 
        "$500 Answer, Category 1" 
        "$500 Answer, Category 2" 
        "$500 Answer, Category 3" 
        "$500 Answer, Category 4" 
        "$500 Answer, Category 5" 
    ]
    questions: [
        "$100 Question, Category 1" 
        "$100 Question, Category 2" 
        "$100 Question, Category 3" 
        "$100 Question, Category 4" 
        "$100 Question, Category 5" 
        "$200 Question, Category 1" 
        "$200 Question, Category 2" 
        "$200 Question, Category 3" 
        "$200 Question, Category 4" 
        "$200 Question, Category 5" 
        "$300 Question, Category 1" 
        "$300 Question, Category 2" 
        "$300 Question, Category 3" 
        "$300 Question, Category 4" 
        "$300 Question, Category 5" 
        "$400 Question, Category 1" 
        "$400 Question, Category 2" 
        "$400 Question, Category 3" 
        "$400 Question, Category 4" 
        "$400 Question, Category 5" 
        "$500 Question, Category 1" 
        "$500 Question, Category 2" 
        "$500 Question, Category 3" 
        "$500 Question, Category 4" 
        "$500 Question, Category 5" 
    ]

    ;________________________________________________________________

}

do config

header: load to-binary decompress 64#{
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
}

do-button: func [num] [
    alert pick answers num 
    alert pick questions num
    if find [1 2 3 4 5] num [val: $100]
    if find [6 7 8 9 10] num [val: $200]
    if find [11 12 13 14 15] num [val: $300]
    if find [16 17 18 19 20] num [val: $400]
    if find [21 22 23 24 25] num [val: $500]
    correct: request-list "Select:" ["Player 1 answered correctly" 
        "Player 1 answered incorrectly" "Player 2 answered correctly"
        "Player 2 answered incorrectly" "Player 3 answered correctly"
        "Player 3 answered incorrectly" "Player 4 answered correctly"
        "Player 4 answered incorrectly"
    ] 
    switch correct  [
        "Player 1 answered correctly" [
            player1/text: to-string ((to-money player1/text) + val)
            show player1
        ] 
        "Player 1 answered incorrectly" [
            player1/text: to-string ((to-money player1/text) - val)
            show player1
        ] 
        "Player 2 answered correctly" [
            player2/text: to-string ((to-money player2/text) + val)
            show player2
        ]
        "Player 2 answered incorrectly"[
            player2/text: to-string ((to-money player2/text) - val)
            show player2
        ]
        "Player 3 answered correctly" [
            player3/text: to-string ((to-money player3/text) + val)
            show player3
        ] 
        "Player 3 answered incorrectly" [
            player3/text: to-string ((to-money player3/text) - val)
            show player3
        ] 
        "Player 4 answered incorrectly"[
            player4/text: to-string ((to-money player4/text) - val)
            show player4
        ]
        "Player 4 answered correctly" [
            player4/text: to-string ((to-money player4/text) + val)
            show player4
        ]
    ]
]

view center-face layout gui: [
    tabs (sizer * 20)
    backdrop effect [gradient 1x1 tan brown]
    style button button effect [gradient blue blue/2] (
        to-pair rejoin [(20 * sizer) "x" (13 * sizer)]
    ) font [size: (sizer * 6)]
    style box box brown (to-pair rejoin [(20 * sizer) "x" (7 * sizer
        )]) font [size: (sizer * 3)]
    image header (to-pair rejoin [(132 * sizer) "x" (8 * sizer)]) [
        contin: request/confirm {
            This will end the current game.  Continue?}
        if contin = false [break]
        loadoredit:  request/confirm "Load previously edited config file?"
        if loadoredit = true [
            do to-file request-file/title/file {
                Choose config file to use:} "File" %default_config.txt
            unview
            view center-face layout gui
            break
        ]
        alert {Edit carefully, maintaining all quotation marks.  
            You can open a previously saved file if needed.
            When done, click SAVE-AS and then QUIT.  
            Be sure choose a filename/folder
            location that you'll be able to find later.
        }
        write %default_config.txt config
        unview
        editor %default_config.txt
        alert {Now choose a config file to use (most likely the file
            you just edited).}
        do to-file request-file/title/file {
            Choose config file to use:} "File" %default_config.txt
        view center-face layout gui
    ]
    space (to-pair rejoin [(8 * sizer) "x" (2 * sizer)])
    pad (sizer * 2)
    across
    box (to-pair rejoin [(132 * sizer) "x" (2 * sizer)]
        ) effect [gradient 1x0 brown black] 
    return
    box Category-1 
    box Category-2 
    box Category-3 
    box Category-4 
    box Category-5
    return
    box (to-pair rejoin [(132 * sizer) "x" (2 * sizer)]
        ) effect [gradient 1x0 brown black] 
    return
    button "$100" [face/feel: none face/text: "" do-button 1]
    button "$100" [face/feel: none face/text: "" do-button 2]
    button "$100" [face/feel: none face/text: "" do-button 3]
    button "$100" [face/feel: none face/text: "" do-button 4]
    button "$100" [face/feel: none face/text: "" do-button 5]
    return
    button "$200" [face/feel: none face/text: "" do-button 6]
    button "$200" [face/feel: none face/text: "" do-button 7]
    button "$200" [face/feel: none face/text: "" do-button 8]
    button "$200" [face/feel: none face/text: "" do-button 9]
    button "$200" [face/feel: none face/text: "" do-button 10]
    return
    button "$300" [face/feel: none face/text: "" do-button 11]
    button "$300" [face/feel: none face/text: "" do-button 12]
    button "$300" [face/feel: none face/text: "" do-button 13]
    button "$300" [face/feel: none face/text: "" do-button 14]
    button "$300" [face/feel: none face/text: "" do-button 15]
    return
    button "$400" [face/feel: none face/text: "" do-button 16]
    button "$400" [face/feel: none face/text: "" do-button 17]
    button "$400" [face/feel: none face/text: "" do-button 18]
    button "$400" [face/feel: none face/text: "" do-button 19]
    button "$400" [face/feel: none face/text: "" do-button 20]
    return
    button "$500" [face/feel: none face/text: "" do-button 21]
    button "$500" [face/feel: none face/text: "" do-button 22]
    button "$500" [face/feel: none face/text: "" do-button 23]
    button "$500" [face/feel: none face/text: "" do-button 24]
    button "$500" [face/feel: none face/text: "" do-button 25]
    return 
    box (to-pair rejoin [(132 * sizer) "x" (2 * sizer)]
        ) effect [gradient 1x0 brown black] 
    return tab
    box "Player 1:" effect [gradient 1x1 tan brown]
    player1: box white "$0" font [color: black size: (sizer * 4)] [
        face/text: request-text/title/default "Enter Score:" face/text
    ]
    box "Player 2:" effect [gradient 1x1 tan brown]
    player2: box white "$0" font [color: black size: (sizer * 4)] [
        face/text: request-text/title/default "Enter Score:" face/text
    ]
    return tab
    box "Player 3:" effect [gradient 1x1 tan brown]
    player3: box white "$0" font [color: black size: (sizer * 4)] [
        face/text: request-text/title/default "Enter Score:" face/text
    ]
    box "Player 4:" effect [gradient 1x1 tan brown]
    player4: box white "$0" font [color: black size: (sizer * 4)] [
        face/text: request-text/title/default "Enter Score:" face/text
    ]
]

7.9 Case 9 - Tetris Game Clone

REBOL [Title: "Textris"]

tui: func [commands [block!]] [
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    arg: parse commands [
        any [
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set arg string! (append string arg)
        ]
        end
    ]
    string
]

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]
    ;
    ["    "]
    [" " down 1 left 1 " " down 1 left 1 " " down 1 left 1 " "]
    ["   " down 1 left 2 " "]
    [right 1 " " down 1 left 2 "  " down 1 left 1 " "]
    [right 1 " " down 1 left 2 "   "]
    [" " down 1 left 1 "  " down 1 left 2 " "]
    ["   " down 1 left 3 " "]
    ["  " down 1 left 1 " " down 1 left 1 " "]
    [right 2 " " down 1 left 3 "   "]
    [" " down 1 left 1 " " down 1 left 1 "  "]
    ["   " down 1 left 1 " "]
    [right 1 " " down 1 left 1 " " down 1 left 2 "  "]
    [" " down 1 left 1 "   "]
    ["  " down 1 left 2 " " down 1 left 1 " "]
    ["  " down 1 left 1 "  "]
    [right 1 " " down 1 left 2 "  " down 1 left 2 " "]
    [right 1 "  " down 1 left 3 "  "]
    [" " down 1 left 1 "  " down 1 left 1 " "]
    ["  " down 1 left 2 "  "]
]
floor:  [
    21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13 21x14 21x15
]
oc:  [ 
    [0x0 0x1 0x2 0x3] [0x0 1x0 2x0 3x0] [0x0 0x1 0x2 1x1]
    [0x1 1x0 1x1 2x1] [0x1 1x0 1x1 1x2] [0x0 1x0 1x1 2x0]
    [0x0 0x1 0x2 1x0] [0x0 0x1 1x1 2x1] [0x2 1x0 1x1 1x2]
    [0x0 1x0 2x0 2x1] [0x0 0x1 0x2 1x2] [0x1 1x1 2x0 2x1]
    [0x0 1x0 1x1 1x2] [0x0 0x1 1x0 2x0] [0x0 0x1 1x1 1x2]
    [0x1 1x0 1x1 2x0] [0x1 0x2 1x0 1x1] [0x0 1x0 1x1 2x1]
    [0x0 0x1 1x0 1x1] 
]
width: [4 1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2]
score: 0

prin tui [clear]
a-line: copy [] loop 11 [append a-line " "] 
a-line: rejoin ["   |"  to-string a-line "|"]
loop 20 [print a-line] prin "   " loop 13 [prin "+"] print ""
print tui compose [
    at 4x21 "TEXTRIS" at 5x21 "-------" 
    at 7x20 "Use arrow keys" at 8x20 "to move/spin."
    at 10x20 "'P' = pause"
    at 13x20 "SCORE:  " (to-string score)
]

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 9
    for i 1 20 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            switch/default to-string copy keys [
                "p" [
                    print tui [
                        at 23x0 "Press [Enter] to continue"
                    ]
                    ask ""
                    print tui [
                        at 24x0 "                              "
                        at 23x0 "                              "
                    ]
                ]
                "^[[D" [if (xpos > 5) [
                        xpos: xpos - 1
                ]]
                "^[[C" [if (xpos < (16 - compose width/(r))) [
                        xpos: xpos + 1
                ]]
                "^[[A" [if (xpos < (16 - compose width/(r)))  [
                        switch to-string r [
                            "1" [r: 2]
                            "2" [r: 1]
                            "3" [r: 6]
                            "4" [r: 3]
                            "5" [r: 4]
                            "6" [r: 5]
                            "7" [r: 10]
                            "8" [r: 7]
                            "9" [r: 8]
                            "10" [r: 9]
                            "11" [r: 14]
                            "12" [r: 11]
                            "13" [r: 12]
                            "14" [r: 13]
                            "15" [r: 16]
                            "16" [r: 15]
                            "17" [r: 18]
                            "18" [r: 17]
                            "19" [r: 19]
                        ]
                    ]
                ]           
            ] []
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
        stop: false
        foreach po compose oc/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: r
                    stop: true
                    break
                ]
            ]
        ]
        foreach po compose oc/(old-r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  old-xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: old-r
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [
            left-col: second pos 
            width-of-shape: length? compose oc/(stop-shape-num)
            right-col: left-col + width-of-shape - 1
            counter: 1
            for current-column left-col right-col 1 [
                add-coord: compose oc/(stop-shape-num)/(counter)
                new-floor-coord: (pos + add-coord)
                append floor new-floor-coord
                counter: counter + 1
            ]
            break
        ]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(old-r)]
    if (first pos) < 2 [
        prin tui [at 23x0]
        print "   GAME OVER!!!^/^/"
        halt
    ]
    score: score + 10
    print tui compose [at 13x28 (to-string score)]
    for row 1 20 1 [
        line-is-full: true
        for colmn 5 15 1 [
            each-coord: to-pair rejoin [row "x" colmn]
            if not find floor each-coord [
                line-is-full: false
                break
            ]
        ]
        if line-is-full = true [
            remove-each cor floor [(first cor) = row]
            new-floor: copy [
                21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13
                21x14 21x15
            ]
            foreach cords floor [
                either ((first cords) < row) [
                    append new-floor (cords + 1x0)
                ][
                    append new-floor cords
                ]
            ]
            floor: copy unique new-floor
            score: score + 1000
            prin tui [clear]
            loop 20 [print a-line] 
            prin "   " loop 13 [prin "+"] print ""
            print tui compose [
                at 4x21 "TEXTRIS" at 5x21 "-------" 
                at 7x20 "Use arrow keys" at 8x20 "to move/spin."
                at 10x20 "'P' = pause"
                at 13x20 "SCORE:  " (to-string score)
            ]
            foreach was-here floor [
                if not ((first was-here) = 21) [
                    prin tui compose [at (was-here)]
                    prin "#"
                ]
            ]
        ]
    ]
]

Here's a quick synopsis of the program:

  • The TUI dialect is defined.
  • The "shape" block, containing the TUI instructions for drawing each shape is defined.
  • The "floor", "oc", and "width" coordinate blocks are defined. The "score" variable is also defined.
  • The backdrop characters (left, right, and bottom barriers), instructions, headers, and score are printed.
  • A forever loop runs the main actions of the program. The subsections of that loop are:
    • A shape is printed on the screen.
    • User keystrokes are watched for.
    • A switch structure decides what to do with entered keystrokes (right arrow = move right, left arrow = move left, up arrow = rotate shape, p = pause).
    • Another switch structure determines which shape to print when the current shape is rotated.
    • The currently printed shape is erased.
    • Two foreach loops check whether the current shape has reached a position at which it should stop falling.
    • If the piece has reached a stopping point, the coordinates occupied by the piece are added to the "floor" block.
    • The shape is printed at its final resting place.
    • If the current shape touches the ceiling, the game ends.
    • The score is updated.
    • If any rows have been completely filled in, their coordinates are removed from the floor block, the coordinates of all other characters are moved down a row, and the screen is reprinted with the new floor coordinates and the new score.
    • The forever loop continues.

7.10 Case 10 - Scheduling Teachers, Part Two

Desktop version:

REBOL [title: "Lesson Scheduler"] 

system/console/break: false
error-message: does [
    ans: request {Internet connection is not available.
        Would you like to see one of the recent local backups?}
    either ans = true [
        editor to-file request-file quit
    ][
        quit
    ]
]

if error? try [
    teacherlist: load ftp://user:pass@website.com/teacherlist.txt
][
    error-message
]
teachers: copy []
foreach teacher teacherlist [append teachers first teacher]
view center-face layout [
    text-list data teachers [folder: value unview]    
]

pass: request-pass/only
correct: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (pass = (second teacher)) [
        correct: true
    ]
]
if correct = false [alert "Incorrect password." quit]

url: rejoin [http://website.com/teacher/ folder]
ftp-url: rejoin [
    ftp://user:pass@website.com/public_html/teacher/ folder
]

if error? try [
    write %schedule.txt read rejoin [url "/schedule.txt"]
][
    error-message
]

; backup (before changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
; local:
write to-file rejoin [
    folder "-schedule_" now/date "_" cur-time ".txt"
] read %schedule.txt
; online:
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %schedule.txt
][
    error-message
]

editor %schedule.txt

; backup again (after changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
write to-file rejoin [
    folder "-schedule_" now/date "_" cur-time ".txt"
] read %schedule.txt
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %schedule.txt
][
    alert "Internet connection not available while backing up."
]

; save to web site:
if error? try [
    write rejoin [ftp-url "/schedule.txt"] read %schedule.txt
][
    alert {Internet connection not available while updating web
    site.  Your schedule has NOT been saved online.}
    quit
]    
browse url

CGI version:

#!/home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Edit Schedule"</TITLE></HEAD><BODY>]

; submitted: decode-cgi system/options/cgi/query-string

; We can't use the above normal line 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 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 schedule.txt has been edited and submitted:

if ((submitted/2 = "save") or (submitted/2 = "save")) [ 
    ; save newly edited schedule:
    write to-file rejoin ["./" submitted/6 "/schedule.txt"] submitted/4
    print ["Schedule 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  -  "]
    print ["Private Server, Login Required:"</strong><BR><BR>]
    print [<FORM ACTION="./edit.cgi">]
    print [" Username: " <input type=text size="50" name="name"><BR><BR>]
    print [" Password: " <input type=text size="50" name="pass"><BR><BR>]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
    print [</FORM>]
    quit
]

; check user/pass against those in teacherlist.txt, 
; end if incorrect:

teacherlist: load %teacherlist.txt
folder: submitted/2 
password: submitted/4
response: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (password = (second teacher)) [
        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 ":" "-"
schedule_text: read to-file rejoin ["./" folder "/schedule.txt"]
write to-file rejoin [
    "./" folder "/" now/date "_" cur-time ".txt"] schedule_text

print [<strong>"Be sure to SUBMIT when done:"</strong><BR><BR>]
print [<FORM method="post" ACTION="./edit.cgi">]
print [<INPUT TYPE=hidden NAME=submit_confirm VALUE="save">]
print [<textarea cols="100" rows="15" name="contents">]
print [schedule_text]
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>]

7.11 Case 11 - An Online Member Page CGI Program

This file is saved as %bb.db:

["Username" "19-Feb-2008/4:55:59-8:00" "1 Address St."
    "123-456-7890" "name@website.com" "40" 
    {REBOL, C, C++, Python, PHP, Basic, AutoIt, others...}
    "6'" {I'm a nobody - just a test account.} "password" 
    [
        {<a href = "./default.jpg" target=_blank>
        <IMG align=baseline alt="" border=0 hspace=0
        src="./default.jpg" width="160" height="120"></a>}
    ]
] 

["Tester McUser" "22-Feb-2008/13:14:44-8:00" "1 Way Lane"
    "234-567-8910" "tester@website.com" "35" "REBOL"
    {5' 11"} "I'm just another test account." "password"
    [
        {<a href = "./files/photo.jpg" target=_blank>
        <IMG align=baseline alt="" border=0 hspace=0
        src="./files/photo.jpg" width="160" height="120"></a>}
    ]
]

index.cgi:

#! /home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

print [<center><table border=1 cellpadding=10 width=90%><tr><td>]

print-all: does [
    print {<TABLE background="" border=0 cellPadding=0 cellSpacing=0
        height="100%" width="100%"><tr><td width = "600">}
    print rejoin [{<font size=5> Members: (} length? bbs {)</font></td>}]
    print {<td><a href="./add.cgi">Join Now</a></td></tr></table><BR>}
    print [<hr>]
    counter: 1
    reverse bbs
    foreach bb bbs [
        print [<BR>]
        if bb/1 <> "file uploaded" [
            print rejoin ["Date/Time: " bb/2]
            print "    "
            print rejoin trim [
                {<a href=
                "./index.cgi?function=edititemnumber&messagenumber=}
                 counter 
                {&Submit=Post+Message">edit | </a>}
            ]
            print rejoin trim [
                {<a href=
                "./index.cgi?function=deleteitemnumber&messagenumber=}
                counter 
                {&Submit=Post+Message">delete</a>}
            ]
            print "    "
            print {
                    <TABLE background="" border=0 cellPadding=2 
                    cellSpacing=2 height="100%" width="100%"><tr>
                    <td width = "600"><BR>
            }
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Name:</FONT><strong>" bb/1 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Address:</FONT><strong>" bb/3 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Phone:</FONT><strong>" bb/4 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Email:</FONT><strong>"
                (replace/all (replace bb/5 "@" " at ") "." " dot ") 
                "</strong>"
            ]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Age:</FONT><strong>" bb/6 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Language:</FONT><strong>" bb/7 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Height:</FONT><strong>" bb/8 "</strong>"]
            print [<BR><BR>]
        ]
        ; automatically convert line endings to HTML " <br>"
        bb_temp: copy bb/9
        replace/all bb_temp newline "  <br>  "
        bb_temp2: copy bb_temp
        ; automatically link any urls starting with http://
        append bb_temp " "
        parse/all bb_temp [any [
            thru "http://" copy link to " " (replace bb_temp2 
                (rejoin [{http://} link]) (rejoin [
                    { <a href="} {http://} link 
                    {" target=_blank>http://} link {</a> }]))
                ] 
            to end
        ]
        print </td>
        print {<td width = "170" valign = "center">}
        print bb/11 ; image link
        print {</td></tr></table>}
        print bb_temp2
        print [<BR><BR><HR>]
        counter: counter + 1    
    ]
    print [</td></tr></td></tr></td></tr></table>]
]
print-all    
print [</td></tr></table></center>]
print read %footer.html

index.html:

<html>
<head>
<title></title>
<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi">
</head>
<body bgcolor="#FFFFFF">
</body>
</html>

add.cgi:

#! /home/path/public_html/rebol/rebol -cs    
REBOL []
print "content-type: text/html^/"
print read %header.html

print [<center><table border=1 cellpadding=10 width=90%><tr><td>]
print [<font size=5>" Add New Member Information:"</font>]
print "    "
print "    "
print "    "
print [<hr>]
print [<FORM method="post" ACTION="./index.cgi">]
print [<br>" Your Name: " <br><input type=text size="60"
    name="username"><BR>]
print [<br>" Password (required to edit member info later): " <br>
    <input type=text size="60" name="password"><BR>]
print [<br>" Address: " <br><input type=text size="60" name="address">
    <BR>]
print [<br>" Phone: " <br><input type=text size="60" name="phone"><BR>]
print [<br>" Email: " <br><input type=text size="60" name="email"><BR>]
print [<br>" Age: " <br><input type=text size="60" name="age"><BR>]
print [<br>" Language: " <br><input type=text size="60" name="language">
    <BR>]
print [<br>" Height: " <br><input type=text size="60" name="height"><BR>
    <BR>]
print [" Other Information/Notes: " <br>]
print [<textarea name=otherinfo rows=5 cols=50></textarea><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Post New Member Info">]
print [</FORM>]

print [</td></tr></table></center>]
print read %footer.html

I integrated the following code into index.cgi, to read and add the info from the above form to the database:

; here's the default code used to read any data from an HTML form:

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

; make sure at least a user name and password was entered:

if submitted/2 <> none [
    if (submitted/2 = "") or (submitted/4 = "") [
    print {
        <strong>You must include at least
         a name and password.</strong>
        <br><br>Press the [BACK] button 
        in your browser to try again.
    }
    print [</td></tr></table></center>]
    print read %footer.html
    halt
]

; now create a new entry block to add to the database:

entry: copy []
append entry submitted/2     ; name
; the time on the server is 3 hours different then our local time:
append entry to-string (now + 3:00)
append entry submitted/6      ; address
append entry submitted/8      ; phone
append entry submitted/10     ; email
append entry submitted/12     ; age
append entry submitted/14     ; language
append entry submitted/16     ; height
append entry submitted/18     ; other info
append entry submitted/4      ; password
append/only entry [
    {<a href = "./default.jpg" target=_blank>
    <IMG align=baseline alt="" border=0 hspace=0 src="./default.jpg"
    width="160" height="120"></a>}
]

; append the new entry to the database, and notify the user:

append/only tail bbs entry
save %bb.db bbs
print {<strong>New Member Added.</strong>
    Click "Edit" to upload a photo.}
print [</td></tr></table></center>]
print read %footer.html

; now display the member page with the new info refreshed:

wait :00:04
refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.html"></head>
}
print refresh-me
quit

add the following to index.cgi:

if submitted/2 = "edititemnumber" [
    ; pick the correct entry from the database, using the submitted
    ; counter variable from the "edit" link in index.cgi:
    selected-block: pick bbs (
        (length? bbs) - (to-integer submitted/4) + 1
    )
    print [<font size=5>" Edit Your Existing Member Information:"</font>]
    print "    "
    ; here's a link we'll need for the section of the outline that
    ; enables image uploading:
    print rejoin [
        {<a href="./upload.cgi?name=} first selected-block 
        {">Upload Image (Add or Change)</a><hr>}
    ]
    print "    "
    print "<br><br>"
    print {<strong><i>PASSWORD REQUIRED TO EDIT! </i></strong>
        (Enter it in the field below.)}
    print "<br><br>"
    print [<FORM method="post" ACTION="./edit.cgi">]
    print rejoin [
        {<br> Your Name:  <br>
            <input type=text size="60" name="username" value="}
         first selected-block {"><BR>}
    ]
    print [<br> <strong> " Member Password " </strong> "(same 
        as when you created the original account): " <br>
        <input type=text size="60"     name="password"><BR>
    ]
    print rejoin [
        {<br> Address:  <br><input type=text size="60" 
            name="address" value="} 
        pick selected-block 3 {"><BR>}
    ]
    print rejoin [
        {<br> Phone:  <br><input type=text size="60" 
            name="phone" value="} 
        pick selected-block 4 {"><BR>}
    ]
    print rejoin [
        {<br> Email:  <br><input type=text size="60" 
            name="email" value="} 
        pick selected-block 5 {"><BR>}
    ]
    print rejoin [
        {<br> Age:  <br><input type=text size="60" 
            name="age" value="} 
        pick selected-block 6 {"><BR>}
    ]
    print rejoin [
        {<br> Language:  <br><input type=text size="60"
            name="language" value="} 
        pick selected-block 7 {"><BR>}
    ]
    print rejoin [
        {<br> Height:  <br><input type=text size="60" 
            name="height" value="} 
        pick selected-block 8 {"><BR><BR>}
    ]
    print [" Other Information/Notes: " <br>]
    print [<textarea name=otherinfo rows=5 cols=50>]
    print [pick selected-block 9]
    print [</textarea><BR><BR>]
    print rejoin [
        {<input type="hidden" name="original_username" value="}
         pick selected-block 1 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_date" value="}
        pick selected-block 2 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_address" value="}
         pick selected-block 3 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_phone" value="}
        pick selected-block 4 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_email" value="}
        pick selected-block 5 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_age" value="} 
        pick selected-block 6 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_language" value="} 
        pick selected-block 7 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_height" value="} 
        pick selected-block 8 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_otherinfo" value="} 
        pick selected-block 9 {">}
    ]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" 
        VALUE="Update Member Information">]
    print [</FORM>]
    print [</td></tr></table></center>]
    print read %footer.html
    quit
]

more:

#! /home/path/public_html/rebol/rebol -cs    
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

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: construct decode-cgi read-cgi

; get password from the entry submitted:

foreach message bbs [
    if all [
        find message submitted/original_username 
        find message submitted/original_date 
        find message submitted/original_address 
        find message submitted/original_phone 
        find message submitted/original_email 
        find message submitted/original_age 
        find message submitted/original_language 
        find message submitted/original_height 
        find message submitted/original_otherinfo
    ] [read-pass: message/10]
]

; save the old block:

old-message:  to-block reduce [
    submitted/original_username 
    submitted/original_date 
    submitted/original_address 
    submitted/original_phone 
    submitted/original_email 
    submitted/original_age 
    submitted/original_language 
    submitted/original_height
    submitted/original_otherinfo 
    read-pass
]

; so that the original pass is not replaced by "blahblah":

either submitted/password = "blahblah" [
    entered-pass: read-pass
] [
    entered-pass: submitted/password
]

; create the new entry for the database:

new-message:  to-block reduce [
    submitted/username 
    submitted/original_date
    submitted/address
    submitted/phone
    submitted/email
    submitted/age
    submitted/language
    submitted/height
    submitted/otherinfo
    entered-pass
]

; check the password, and replace:

if submitted/password <> "" [
    either (
        read-pass = submitted/password
    ) or (
        submitted/password = "blahblah"
    ) [
        foreach message bbs [replace message old-message new-message]
    ] [
        print {
            <strong>Forgot your member password?</strong> <br><br>
            It's being emailed to the address for this entry, right now...
            Wait for this page to refresh, then <strong>check your email!
            </strong>
        }
        print read %footer.html
        wait 3
        set-net [user@website.com smtp.website.com]
        send (to-email submitted/original_email) (to-string rejoin [
            "Forgot your member password?" newline newline 
            trim {Someone was editing an entry with this email address, 
                but the incorrect password was used.  Here is the correct
                password, in case you've forgotten:}
             newline newline read-pass
        ])
    ]
]
save %bb.db bbs

; diplay the edited results on the main user page:

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

backup code added to the script above:

backup-num: load %backup-num.txt
backup-num: backup-num + 1
write %backup-num.txt backup-num
filename: to-file rejoin ["./backup/bb-" (to-string backup-num) ".txt"]
save filename bbs

this code gets added to index.cgi:

if submitted/2 = "deleteitemnumber" [
    selected-block: pick bbs (
        (length? bbs) - (to-integer submitted/4) + 1
    )
    print [<font size=5>" Delete An Existing Member Account:"</font><hr>]
    print [<FORM method="post" ACTION="./delete.cgi">]
    print rejoin [
        {<br> Your Name:  <br>
            <input type=text size="60" name="username" value="} 
        first selected-block {"><BR>}
    ]
    print [<br>" Member Password (
        same as when you created the original account): " 
        <br><input type=text size="60"     name="password"><BR><BR>
    ]
    print rejoin [
        {<input type="hidden" name="original_username" value="} 
        pick selected-block 1 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_date" value="} 
        pick selected-block 2 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_address" value="} 
        pick selected-block 3 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_phone" value="} 
        pick selected-block 4 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_email" value="} 
        pick selected-block 5 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_age" value="} 
        pick selected-block 6 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_language" value="} 
        pick selected-block 7 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_height" value="} 
        pick selected-block 8 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_otherinfo" value="} 
        pick selected-block 9 {">}
    ]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Delete Member Info">]
    print [</FORM>]
    print [</td></tr></table></center>]
    print read %footer.html
    quit
]

delete.cgi:

#! /home/path/public_html/rebol/rebol -cs    
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

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: construct decode-cgi read-cgi

foreach message bbs [
    if all [
        find message submitted/original_username 
        find message submitted/original_date 
        find message submitted/original_address 
        find message submitted/original_phone 
        find message submitted/original_email 
        find message submitted/original_age
        find message submitted/original_language 
        find message submitted/original_height 
        find message submitted/original_otherinfo
    ] [read-pass: message/10] 
]

old-message:  to-block reduce [
    submitted/original_username 
    submitted/original_date 
    submitted/original_address 
    submitted/original_phone 
    submitted/original_email 
    submitted/original_age 
    submitted/original_language 
    submitted/original_height 
    submitted/original_otherinfo 
    read-pass
]

if submitted/password <> "" [
    if (
        read-pass = submitted/password
    ) or (
        submitted/password = "blahblah"
    ) [    
        backup-num: load %backup-num.txt
        backup-num: backup-num + 1
        write %backup-num.txt backup-num
        filename: to-file rejoin [
            "./backup/bb-" (to-string backup-num) ".txt"
        ]
        save filename bbs

        foreach message bbs [replace message old-message ""]
    ]
]

remove-each message bbs [
    any [
        message = [""] 
        (all [
            message/1 = "" message/2 = "" message/3 = "" message/4 = ""
            message/5 = "" message/6 = "" message/7 = "" message/8 = ""
            message/9 = ""
            ]
        )
    ]
]

save %bb.db bbs

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

image uploader cgi:

#! /home/path/public_html/rebol/rebol -cs
REBOL [Title: "HTTP File Upload"]
print "content-type: text/html^/"
print read %header.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
]

; here's Andreas's magic function to read form/multipart data:

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
]

; now we can put the uploaded binary, and all the text entered by the
; user via the HTML form, into a REBOL object.  we can refer to the
; uploaded photo using the syntax:  cgi-object/photo/content

post-data: read-cgi
cgi-object: construct decode-multipart-form-data (
    system/options/cgi/content-type copy post-data
)

; I created a "./files" subdirectory to hold these images.  Now
; write the file to the web server using the original filename,
; but without any Windows path characters, and notify the user:

adjusted-filename: copy cgi-object/photo/filename
adjusted-filename: replace/all adjusted-filename "/" "-"
adjusted-filename: replace/all adjusted-filename "\" "-"
adjusted-filename: replace/all adjusted-filename " " "_"
adjusted-filename: replace/all adjusted-filename ":" "_"
adjusted-filename: to-file rejoin ["./files/" adjusted-filename]
write/binary adjusted-filename cgi-object/photo/content
print [<strong>]
print {Upload Complete.  }
print [</strong>]
print [<br><br>]

; now add an HTML link to this file, to the database:

bbs: load %bb.db    
entry: copy []
link-added: rejoin [
    {<a href = "} to-string adjusted-filename {" target=_blank>}
    {<IMG align=baseline alt="" border=0 hspace=0 src="} 
    to-string adjusted-filename 
    {" width="160" height="120">} </a>
]  ; display image inline
append entry link-added
foreach message bbs [
    if (all [
        cgi-object/username = message/1 
        cgi-object/password = message/10
    ]) [
        if ((length? message) < 11) [append message ""] 
        message/11: entry
    ]
]
save %bb.db bbs

; show additions by refreshing the index.cgi page:

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

more:

#! /home/path/public_html/rebol/rebol -cs    
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Edit Database!!!"</TITLE></HEAD><BODY>]

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 schedule.txt has been edited and submitted:

if ((submitted/2 = "save") or (submitted/2 = "save")) [ 
    ; save newly edited schedule:
    write %./bb.db submitted/4
    print ["Database Saved."]
    ; print {<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./bb.db">}
    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>]
    print [<FORM ACTION="./editor.cgi">]
    print [" Username: " <input type=text size="50" name="name"><BR><BR>]
    print [" Password: " <input type=text size="50" name="pass"><BR><BR>]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
    print [</FORM>]
    quit
]

; check user/pass, end if incorrect:

response: false
if ((submitted/2 = "username") and (submitted/4 = "password")) [
    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 ":" "-"
schedule_text: read %./bb.db
write to-file rejoin [
    "./backup/" now/date "_" cur-time ".txt"
] schedule_text

; here's the form that lets the user edit the text:

print [<center>]
print [<strong>"Be sure to click [SUBMIT] when done:"</strong><BR><BR>]
print [<strong>"(This will OVERWRIGHT the current database!)"</strong>
    <BR><BR>]
print [<FORM method="post" ACTION="./editor.cgi">]
print [<INPUT TYPE=hidden NAME=submit_confirm VALUE="save">]
print [<textarea cols="100" rows="25" name="contents">]
print [schedule_text]
print [</textarea><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</center>]
print {<br><br><br><br><br><br><br><br><hr>}

; here's a linked listing of all the backup files available for
; copy/paste:

foreach file (read %./backup/) [
    print rejoin [
        {<a href="./backup/} file {" target=_blank>} file {</a> }]
    ]
print [</BODY></HTML>]

You can see a live demo at http://guitarz.org/tester and download the complete set of scripts in this case study at http://guitarz.org/tester/member_board.zip.

7.12 Case 12 - A CGI Event Calendar

#! /home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print {<HTML><HEAD><TITLE>Event Calendar</TITLE></HEAD><BODY>}

bbs: load %bb.db
date: now/date
html: copy rejoin [
    {<CENTER><TABLE border=1 valign=middle width=99% height=99%>
        <TR><TD colspan=7 align=center height=8%><FONT size=5>}
    pick system/locale/months date/month { } date/year
    {</FONT></TD></TR><TR>}
]

days: ["Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"]
foreach day days [
    append html rejoin [
        {<TD bgcolor="#206080" align=center width=10% height=5%>
        <FONT face="courier new,courier" color="FFFFFF" size="+1">}
        day 
        {</FONT></TD>}
    ]
]
append html {</TR><TR>}

sdate: date  sdate/day: 0  
loop sdate/weekday // 7 + 1 [append html {<TD bgcolor=gray></TD>}]

while [sdate/day: sdate/day + 1 sdate/month = date/month][
    event-labels: {}
    foreach entry bbs [
        date-in-entry: 1-Jan-1001
        attempt [date-in-entry: (to-date entry/3)]
        if (date-in-entry = sdate) [
            event-labels: rejoin [
                {<font size=1>}
                event-labels 
                "<strong><br><br>"
                {<a href="http://website.com/path/calendar">}
                entry/1 
                {</a>}
                "</strong>"
                {</font>}
            ]
        ]
    ]
    append html rejoin [
        {<TD bgcolor="#}
        either date/day = sdate/day ["AA9060"]["FFFFFF"]
        ; HERE, THE EVENTS ARE PRINTED IN THE APPROPRIATE DAY:
        {" height=14% valign=top>} sdate/day event-labels
        {</TD>}
    ]
    if sdate/weekday = 6 [append html {</TR><TR>}]
]

loop 7 - sdate/weekday [append html rejoin [{<TD bgcolor=gray></TD>}]]

append html {</TR></TABLE></CENTER></BODY></HTML>}
print html

7.13 Case 13 - Ski Game, Snake Game, and Space Invaders Shootup

Ski game:

REBOL [title: "Ski Game"]

tree:  load to-binary decompress 64#{
eJzt18sNwjAQBFDTBSVw5EQBnLjQE1XRngmBQEj8Wa/3M4oYOZKBKHkaWwTO1/sh
jDkNx3N6HI7LcOzCfnz/9v5cMnEai7lj4mokT9C7XczUsrhvGSku6RkgDIbHAEP0
2EiIMBdMDuaOWZCSL91bQvCsSY4MHE9umXz7ydVi3xgltYvEKboexzVSlpTa614d
NonpUauIv176dX0ZTRgJlVgzNl25A3gkGwld1bkrNFqqedQfEI02AU9PjDeMpac/
ShKeTXylROqCImlXRFd9zkQoh4tp+GpqlSTnLnum4HTEzK/gjpmTpDxSASlHFqYU
EE/8nddG9n+9LIm8t9OeIEra2JZWDRSG4VEioa0UFCZFqv/aMQh2Rf790EnGgcJU
SVAer0Bhcp7/epVJvkHzBHjPfz+XSe6BwryC5gmQno3mAY3tpba2KAAA
}
skier-left: load to-binary decompress 64#{
eJyN0U8og2EcB/DvNrz+E5fJZSmRf9Ej76h3Ne1AIspyMQflpJDFU/KO1cQmSnGa
A3PYkvInB3kvuyzlgJolh+fCRUq5iBvP8+5lTvKrX33ep+/zp9/b2Tthhl6zvGt5
W3nX8TYhS1//MOGnSjNEa/AUxd0UVQ3raL9IYbBvA2OBI9Q0DqB6fAujl08Yi97D
Hr3F5EQYSss2OrrWEFo5xB+VO5Vx/skvnxmQbDCFvxcjMJ/b0s6LAZXGA3O0ZtTt
pW3WbJmDeMC8a1gE9o3bTBFI9YvGhrOKSueyEQpu9ri60vQFXFqPMx1K+sNWrdOh
73Y/uMr85fKdcIrJ0z6vxSfsYV5KCU2JEPNIlD9dFZ65AfXwD+HsKdAZiiLdqtvt
Hh65E5ZklTGmDvWLgxxKkjAivwt7XxhJEvIsrCY8ikLs0Tj3yGeCKaQtdsX9fv3G
N1jCJdyv84lHJkNriiM7Li29OIDV0jcU8kuIHaiPLEDEsG9DQYxiQTi0A8sBpEvh
OT65GmBYH9Jx5nf8TFFUFf5ZX2hFdG1uAgAA
}
skier-right: load to-binary decompress 64#{
eJxz8s1jYgCDMiDWAGIJINYCYkYGFrD4D0YGOBBAMBn4++Yz6HjVMSgY1oP5gWdu
M/gHTmCwNutlKJ26l6F03VUGp3XnGGo+/mGILVnMoFkwhaHm7GcGz4m7GbABFwST
eQWSNXMQbM+3DAwlULbmEgaWXih75QUGzvkQJstMBwbPRRA2L1D5yS8QNudioNQF
qNYPDExAZRCtDg78c6Fa7wZK3Ycq940O3L1fAcLWigpctUsZzHTSj5Jd+l7NAKS6
3HnXk6jHSiBF7sUmxi7Gl9VAZrqVOxsZuTirg8TTS0qAQs5FIPF0BhYXFkgog/zg
7gJlq5SXpaWVF4O9lZKuXl6eVl4AZLIfKS82LzYuB2nlOFxWXl5ubA6ytm1KWU65
cXExkMl09lNNR3q5eTFQPYfHE7YT6cXlJgcYGI7cPMAOMtKhgcH9wE8FBuPycgOG
BoYKtl8ODL4gjccY2HSAfr4BVMvgAwyazwwsXSA7ORgY2BQYeH+Cw+sAKPo5wEHj
kQAO/GZwIIHDgc0AaxQSBAAFOXD7bgIAAA==
}
random/seed now
the-score: 0
board: reduce ['image 300x20 skier-right black]
for i 1 20 1 [
    pos: random 600x540
    pos: pos + 0x300
    append board reduce ['image pos tree black]
]
view center-face layout/tight [
    scrn: box white 600x440 effect [draw board] rate 0 feel [
        engage: func [f a e] [
            if a = 'key [
                if e/key = 'right [
                    board/2: board/2 + 5x0
                    board/3: skier-right
                ]
                if e/key = 'left [
                    board/2: board/2 - 5x0
                    board/3: skier-left
                ]
                show scrn
            ]
            if a = 'time [
                new-board: copy []
                foreach item board [
                    either all [
                        ((type? item) = pair!) 
                        ((length? new-board) > 4)
                    ] [ 
                        append new-board (item - 0x5) 
                    ] [
                        append new-board item
                    ]
                    coord: first back back (tail new-board)
                    if ((type? coord) = pair!) [
                        if ((second coord) < -60) [
                            remove back tail new-board
                            remove back tail new-board
                            remove back tail new-board
                            remove back tail new-board
                        ]
                    ]
                ]
                board: copy new-board
                if (length? new-board) < 84 [
                    column: random 600
                    pos: to-pair rejoin [column "x" 440]
                    append board reduce ['image pos tree black]
                ]
                collision-board: remove/part (copy board) 4
                foreach item collision-board [
                    if (type? item) = pair! [
                        if all [
                          ((item/1 - board/2/1) < 15)
                          ((item/1 - board/2/1) > -40)
                          ((board/2/2 - item/2) < 30)
                          ((board/2/2 - item/2) > 5)
                        ] [
                            alert "Ouch - you hit a tree!"
                            alert rejoin ["Final Score: " the-score]
                            quit
                        ]
                    ]
                ]
                the-score: the-score + 1 
                score/text: to-string the-score
                show scrn
            ]
        ]
    ]
    origin across h2 "Score:" 
    score: h2 bold "000000"
    do [focus scrn]
]

7.13.1 Addendum

It should be noted that I did lots of trial and error coding along the way, while writing and testing this program. One thing that I tried initially was to have the skier move left-right by following left-right mouse gestures. I scrapped that idea because my code performed too slowly for this application, but the resulting code may still be useful in other projects. It's included here for completeness.

I defined this starting variable at the beginning of the program:

mouse-pos: 0x0

and added this code to the "feel" block, directly beneath the "engage" function:

over: func [f a p] [
    if not mouse-pos = p [  ; i.e., if mouse has moved
        either p/1 > mouse-pos/1 [  ; true = mouse has moved right
            ; update the skier image data in the "board" block:
            board/3: skier-right
        ] [
            board/3: skier-left
        ]
        ; set skier's position based on the column position of the
        ; mouse:
        board/2: to-pair rejoin compose [(p/1 - 35) "x" 20]
        mouse-pos: p
        show scrn
    ]
]

In order for the REBOL to continuously check for mouse events, the following "all-over" option must be added to the 'view layout' code:

view/options layout [...] [all-over]

Remove the key action code in the engage function, and replace it with the above changes. The skier will move left-right based upon left-right movements of the mouse.

Another way to accomplish the same goal, without using the "all-over" option, is to use the feel "detect" function:

detect: func [f e] [
    if e/type = 'move [
        p: e/offset
        if not mouse-pos = p [  
            either p/1 > mouse-pos/1 [  
                board/3: skier-right
            ] [
                board/3: skier-left
            ]
            board/2: to-pair rejoin compose [(p/1 - 35) "x" 20]
            mouse-pos: p
            show scrn
        ]
    ]
    e
]

That type of mouse control wasn't the best solution here, but could certainly be useful in other programs.

Snake Game:

REBOL [Title: "Snake Game"]

snake: to-image layout/tight [button red 10x10]
food: to-image layout/tight [button green 10x10]
the-score: 0  direction: 0x10  newsection: false  random/seed now
rand-pair: func [s] [
    to-pair rejoin [(round/to random s 10) "x" (round/to random s 10)]
]
b: reduce [
    'image food ((rand-pair 190) + 50x50) 
    'image snake ((rand-pair 190) + 50x50)
]
view center-face layout/tight gui: [
    scrn: box white 300x300 effect [draw b] rate 15 feel [
        engage: func [f a e] [
            if a = 'key [
                if e/key = 'up [direction: 0x-10] 
                if e/key = 'down [direction: 0x10]
                if e/key = 'left [direction: -10x0]
                if e/key = 'right [direction: 10x0]
            ]
            if a = 'time [
                if any [b/6/1 < 0 b/6/2 < 0 b/6/1 > 290 b/6/2 > 290] [
                    alert "You hit the wall!" quit
                ]
                if find (at b 7) b/6 [alert "You hit yourself!" quit] 
                if within? b/6 b/3 10x10 [
                    append b reduce ['image snake (last b)]
                    newsection: true
                    b/3: (rand-pair 290)
                ]
                newb: copy/part head b 5  append newb (b/6 + direction)
                for item 7 (length? head b) 1 [
                    either (type? (pick b item) = pair!) [
                        append newb pick b (item - 3)
                    ] [
                        append newb pick b item
                    ]
                ]
                if newsection = true [
                    clear (back tail newb)
                    append newb (last b)
                    newsection: false
                ]
                b: copy newb
                show scrn
                the-score: the-score + 1 
                score/text: to-string the-score
            ]
        ]
    ]
    origin across h2 "Score:" 
    score: h2 bold "000000"
    do [focus scrn]
]

Space Invaders Shootup:

REBOL [title: "Space Invaders Shootup"]

alien1:  load to-binary decompress 64#{
eJx9UzFLQzEQjijUOognHTIVhCd0cXJ1kLe3g7SbFKcsWQoWZ7MFhNKxg0PpH3Cx
WbKUqpPoUNcOPim1Q+kPkCJekvf0NTx7cLl7d8l33+XywvL+FrFyhVpCPUY9QN0g
LnG7ScjjrtM98iedToeM3kbW7/f71k4/p6R+USe9Xo/UqjUbi94jMhgMrL/8XpLm
ZZP4spPyzxVTT35MM2Zir4vFYu4dM7GP2M483Fa8f8w0O/Vy24yzo8RXipfJmdb8
kJxwrdJ7K4gxiSs7/09czYpdW6vcsI+AtrEKQ7ScDPlLHO/aNQ8huzaVeSDaHrNi
3IlBjDI6mqVsWvIA0E5ZJ2OtlUIuAKHmqoS5kHOt9UPMP0sm3TU5PHdHQVIZMs3v
qZTPmrMAQAj6ZXOSUtkwPKRwloKQNlexCDOvR4fpclGq76KNzC2mQPiG681i5gAw
ZusVJEAh5JojBzrEGQYC2dncuh7+y83d7ASVAu8MpAQqkT9+3Gg3Q+wHI2AZSAFm
1+99FzMQkzllVUxeTFUrc4vC4Q4VV4wlLyaerjD1XPe+tLxK8SNbqTrJOIf/Bd4X
V+VU7AfjSm0ZEgQAAA==
}
ship1: load to-binary decompress 64#{
eJx1Us9L3EAU/rTbMdHE9VlYukSQFhUpvXjQXrfizR8XkYAnt0oQVsTLGlgEFdSL
l3jqpXgre6sEJAgDsidPIul/sHjopeIfILj0JdnV3ez0y7zJzLx533vvY2YXhzOI
scs2yfaF7QNbDxLHjzfAu4HEhpKryAgDfccC4rfAws0IjF/96HsWyH7XMNXIon9Z
QJvWsNQw8XZP4NPlKD73Whi/HcZO4z207fv7jyo8/jk4r1TdFQXcSrV+flEtq3x2
5amuB44lyU+BpHRKHq4dKXnZCbLkxl9kOF5BarPVDFWyBAWcEAVFsjrhENGmhyPK
UXe+XNHf9HqZW9GgyzUUoloqXcXE1wv6iSTHohSkQ8yJQ5l2RCiSvPIGbaVkTFuu
Ge5/erfdurb+wM3ETZHPyjaX5NzNHPATOHMsn894sMZJWX4uH78OYSvTrUU+paI2
q8nQl5JHMFaSOZLBbHPnoR2ndHUa5NtPwubfFKziT1YqRDdY2VV3JckT3X2ZIlwW
KQjmUxGhGQ0Ecm5OlhBvUsSi/NpXmjLRoFx4YWuL0789fN24m+jsK2x+wGE+JjLR
DePiqdbKZqZojf1qLZ2ptdO3ZrxXwjCODzuThK3Af4EF8jYSBAAA
}
alien-fire: load to-binary decompress 64#{
eJxz8o1jZACDMiDWAGI+IJYFYkYGFrD4CyAW5oZgAYhSBhZmFoaWphaG48eOMwQF
BDFoaGgwPH36lGHZsmUM4uLiDFk5WQyzZs1iuHHzBsOfv38Ydu7cyWBhZsFQXlrO
EBEVATTBaWlolAoDA/vp3bt37wHyZwPpTUCaedqpUBWGS6HLMj8AedpA0Z1QGqTK
KXrNtCdgF/BLtrCD6GywOAPDabA6BobCTAMwXTfzFMh8uM7ZUBpi/p3QZdMMwLp2
796GbH7omrR2sH6Omc+h5m4C09pQuiKzHWp+O1R+D1QeQjstPQINIwag+wBUhlwj
XgEAAA==
}
ship-fire: load to-binary decompress 64#{
eJxz8t3FAAFlQKwBxOxALAjEjAwsYHEXIBbmhmABqFo2FhYG9l4eBvajbAwKSTIM
/H8FGFjUOBg4tnEyGP1VYWAXZWOwadNg4KhiYdA5JMLAacbJIHNLhUFnkgiDIpMg
2IyDd2UYVMqdGNLLyxoOz7RpCJ5p2pDi4sYAwlFpSz+AcEoJkF8O5KstZWhUkvig
4uLEoAIUO7f7zQcA8m8lvboAAAA=
}
bottom: 270  end: sidewall: false  random/seed now
b: ['image 300x400 ship1 'line -10x270 610x270]
for row 60 220 40 [
    for column 20 380 60 [
        pos: to-pair rejoin [column "x" row]
        append b reduce ['image pos alien1]
    ]
]
view center-face layout/tight [
    scrn: box black 600x440 effect [draw b] rate 1000 feel [
        engage: func [f a e] [
            if a = 'key [
                if e/key = 'right [b/2: b/2 + 5x0]
                if e/key = 'left [b/2: b/2 - 5x0]
                if e/key = 'up [
                    if not find b ship-fire [
                        fire-pos: b/2 + 25x-20
                        append b reduce ['image fire-pos ship-fire]
                    ]
                ]
                system/view/caret: none
                show scrn
                system/view/caret: head f/text
            ]
            if a = 'time [
                if (random 1000) > 900 [ 
                    f-pos: to-pair rejoin [random 600 "x" bottom]
                    append b reduce ['image f-pos alien-fire]
                ]
                for i 1 (length? b) 1 [
                    removed: false
                    if ((pick b i) = ship-fire) [
                        for c 8 (length? head b) 3 [
                            if (within? (pick b c) (
                            (pick b (i - 1)) + -40x0) 50x35)
                            and ((pick b (c + 1)) <> ship-fire) [
                                removed: true 
                                d: c
                                e: i - 1
                            ]
                        ]
                        either ((second (pick b (i - 1))) < -10) [
                            remove/part at b (i - 2) 3
                        ] [
                            do compose [b/(i - 1): b/(i - 1) - 0x9]
                        ]
                    ]
                    if ((pick b i) = alien1) [
                        either ((second (pick b (i - 1))) > 385) [
                            end: true
                        ] [
                            if ((first (pick b (i - 1))) > 550) [
                                sidewall: true
                                for item 4 (length? b) 1 [
                                    if (pick b item) = alien1 [
                                        do compose [
                                          b/(item - 1): b/(item - 1) + 0x2
                                        ]
                                    ]
                                ]
                                bottom: bottom + 2       
                                b/5: to-pair rejoin [-10 "x" bottom]
                                b/6: to-pair rejoin [610 "x" bottom]
                            ]
                            if ((first (pick b (i - 1))) < 0) [
                                sidewall: false
                                for item 4 (length? b) 1 [
                                    if (pick b item) = alien1 [
                                        do compose [
                                          b/(item - 1): b/(item - 1) + 0x2
                                        ]
                                    ]
                                ]
                                bottom: bottom + 2
                                b/5: to-pair rejoin [-10 "x" bottom]
                                b/6: to-pair rejoin [610 "x" bottom]
                            ]
                            if sidewall = true [
                                do compose [b/(i - 1): b/(i - 1) - 2x0]
                            ]
                            if sidewall = false [
                                do compose [b/(i - 1): b/(i - 1) + 2x0]
                            ]
                        ]
                    ]
                    if ((pick b i) = alien-fire) [
                        if within? ((pick b (i - 1)) + 0x14) (
                            (pick b 2) + -10x0) 65x35 [
                            alert "You've been killed by alien fire!" quit
                        ]
                        either ((second (pick b (i - 1))) > 400) [
                            remove/part at b (i - 2) 3
                        ] [
                            do compose [b/(i - 1): b/(i - 1) + 0x3]
                        ]
                    ]
                    if removed = true [
                        remove/part (at b (d - 1)) 3
                        remove/part (at b (e - 1)) 3
                    ]
                ]
                system/view/caret: none
                show scrn
                system/view/caret: head f/text
                if not (find b alien1) [
                    alert "You killed all the aliens. You win!" quit
                ] 
                if end = true [alert "The aliens landed! Game over." quit]
            ]
        ]
    ]
    do [focus scrn]
]

7.14 Case 14 - Media Player (Wave/Mp3 Jukebox)

A nice little .wav playing application:

REBOL []

play-sound: func [sound-file] [
    wait 0
    wait-flag: true
    ring: load sound-file
    sound-port: open sound://
    insert sound-port ring
    wait sound-port
    close sound-port
    wait-flag: false
]
wait-flag: false
change-dir %/c/Windows/media
waves: []
foreach file read %. [
    if %.wav = suffix? file [append waves file]
]
view layout [
    vh2 "Click a File to Play:"
    file-list: text-list data waves [
        if wait-flag <> true [
            if error? try [play-sound value] [
                alert "malformed wave"
                close sound-port
                wait-flag: false
            ]
        ]
    ]
    btn "Change Folder" [
        change-dir request-dir
        waves: copy []
        foreach file read %. [
            if %.wav = suffix? file [append waves file]
        ]
        file-list/data: waves
        show file-list
    ]
]

Lame encoder version:

REBOL []

do http://musiclessonz.com/rebol_tutorial/lame.r
play-sound: func [sound-file] [
    wait 0
    wait-flag: true
    ring: load sound-file
    sound-port: open sound://
    insert sound-port ring
    wait sound-port
    close sound-port
    wait-flag: false
]
wait-flag: false
change-dir %/c/Windows/media
waves: []
foreach file read %. [
    if ((%.wav = suffix? file) or
        (%.mp3 = suffix? file)) [append waves file]
]
view center-face layout [
    vh2 "Click a File to Play:"
    file-list: text-list data waves [
        either %.mp3 = suffix? value [
            message/text: "Decoding mp3..." show message
            call/wait rejoin ["lame.exe --decode "
                (to-local-file value) " temp.wav"]
            message/text: "" show message
            if wait-flag <> true [
                if error? try [play-sound %temp.wav] [
                    alert "malformed wave"
                    close sound-port
                    wait-flag: false
                ]
            ]
        ] [
            if wait-flag <> true [
                if error? try [play-sound value] [
                    alert "malformed wave"
                    close sound-port
                    wait-flag: false
                ]
            ]
        ]
    ]
    btn "Change Folder" [
        change-dir request-dir
        waves: copy []
        foreach file read %. [
            if ((%.wav = suffix? file) or
            (%.mp3 = suffix? file)) [append waves file]
        ]
        file-list/data: waves
        show file-list
    ]
    message: h2 "                   "
]

Here's the final code:

REBOL [title: "Jukebox - Wav/Mp3 Player"]

if not exists? %libwmp3.dll [
    write/binary %libwmp3.dll
    read/binary http://musiclessonz.com/rebol_tutorial/libwmp3.dll
]

lib: load/library %libwmp3.dll

Mp3_Initialize: make routine! [
    return: [integer!]
] lib "Mp3_Initialize"

Mp3_OpenFile: make routine! [
    return: [integer!] 
    class [integer!] 
    filename [string!]
    nWaveBufferLengthMs [integer!]
    nSeekFromStart [integer!] 
    nFileSize [integer!]
] lib "Mp3_OpenFile"

Mp3_Play: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Play"

Mp3_Stop: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Stop"

Mp3_Destroy: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Destroy"

Mp3_GetStatus: make routine! [
    return: [integer!] 
    initialized [integer!] 
    status [struct! []]
] lib "Mp3_GetStatus"

status: make struct! [
    fPlay [integer!] 
    fPause [integer!] 
    fStop [integer!] 
    fEcho [integer!] 
    nSfxMode [integer!] 
    fExternalEQ [integer!] 
    fInternalEQ [integer!] 
    fVocalCut [integer!] 
    fChannelMix [integer!] 
    fFadeIn [integer!] 
    fFadeOut [integer!] 
    fInternalVolume [integer!] 
    fLoop [integer!] 
    fReverse [integer!] 
] none

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

wait-flag: false
change-dir %/c/Windows/media
waves: []
foreach file read %. [
    if ((%.wav = suffix? file) or
        (%.mp3 = suffix? file)) [append waves file]
]

initialized: Mp3_Initialize

view center-face layout [
    vh2 "Click a File to Play:"
    file-list: text-list data waves [
        Mp3_GetStatus initialized status
        either %.mp3 = suffix? value [
            if (wait-flag <> true) and (status/fPlay = 0) [
                file: rejoin [to-local-file what-dir "\" value]
                Mp3_OpenFile initialized file 1000 0 0
                Mp3_Play initialized
            ]
        ] [
            if (wait-flag <> true) and (status/fPlay = 0) [
                if error? try [play-sound value] [
                    alert "malformed wave"
                    close sound-port
                    wait-flag: false
                ]
            ]
        ]
    ]
    across
    btn "Change Folder" [
        change-dir request-dir
        waves: copy []
        foreach file read %. [
            if ((%.wav = suffix? file) or
            (%.mp3 = suffix? file)) [append waves file]
        ]
        file-list/data: waves
        show file-list
    ]
    btn "Stop" [
         close sound-port
         wait-flag: false
         if (status/fPlay > 0) [Mp3_Stop initialized]
    ]
]

Mp3_Destroy initialized
free lib

Another version with various features added (MP3 only):

REBOL [Title: "Jukebox"]

if not exists? %libwmp3.dll [
    write/binary %libwmp3.dll
    read/binary http://musiclessonz.com/rebol_tutorial/libwmp3.dll
]
lib: load/library %libwmp3.dll
Mp3_Initialize: make routine! [
    return: [integer!]
] lib "Mp3_Initialize"
Mp3_OpenFile: make routine! [
    return: [integer!] 
    class [integer!] 
    filename [string!]
    nWaveBufferLengthMs [integer!]
    nSeekFromStart [integer!] 
    nFileSize [integer!]
] lib "Mp3_OpenFile"
Mp3_Play: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Play"
Mp3_Stop: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Stop"
Mp3_Destroy: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Destroy"
Mp3_GetStatus: make routine! [
    return: [integer!] 
    initialized [integer!] 
    status [struct! []]
] lib "Mp3_GetStatus"
status: make struct! [
    fPlay [integer!] 
    fPause [integer!] 
    fStop [integer!] 
    fEcho [integer!] 
    nSfxMode [integer!] 
    fExternalEQ [integer!] 
    fInternalEQ [integer!] 
    fVocalCut [integer!] 
    fChannelMix [integer!] 
    fFadeIn [integer!] 
    fFadeOut [integer!] 
    fInternalVolume [integer!] 
    fLoop [integer!] 
    fReverse [integer!] 
] none
Mp3_Time: make struct! [
    ms [integer!] 
    sec [integer!]
    bytes [integer!] 
    frames [integer!] 
    hms_hour [integer!] 
    hms_minute [integer!] 
    hms_second [integer!] 
    hms_millisecond [integer!] 
] none
TIME_FORMAT_SEC: 2
SONG_BEGIN: 1
SONG_CURRENT_FORWARD: 4
Mp3_Seek: make routine! [
    return: [integer!] 
    initialized [integer!]
    fFormat [integer!]
    pTime [struct! []]
    nMoveMethod [integer!]
] lib "Mp3_Seek"
Mp3_PlayLoop: make routine! [
    return: [integer!] 
    initialized [integer!]
    fFormatStartTime [integer!]
    pStartTime [struct! []]
    fFormatEndTime [integer!] 
    pEndTime [struct! []]
    nNumOfRepeat [integer!] 
] lib "Mp3_PlayLoop"
Mp3_GetSongLength: make routine! [
    return: [integer!]
    initialized [integer!]
    pLength [struct! []]
] lib "Mp3_GetSongLength"
Mp3_GetPosition: make routine! [
    return: [integer!] 
    initialized [integer!]
    pTime [struct! []]
] lib "Mp3_GetPosition"
Mp3_SetVolume: make routine! [
    return: [integer!] 
    initialized [integer!]
    nLeftVolume [integer!]
    nRightVolume [integer!]
] lib "Mp3_SetVolume"
Mp3_GetVolume: [
    initialized [integer!]
    pnLeftVolume [integer!]
    pnRightVolume [integer!]
    return: [integer!]
] lib "Mp3_GetVolume"
Mp3_VocalCut: make routine! [
    return: [integer!] 
    initialized [integer!]
    fEnable [integer!]
] lib "Mp3_VocalCut"
Mp3_ReverseMode: make routine! [
    return: [integer!] 
    initialized [integer!]
    fEnable [integer!]
] lib "Mp3_ReverseMode"
Mp3_Close: make routine! [
    return: [integer!] 
    initialized [integer!]
] lib "Mp3_Close"
waves: []
foreach file read %. [
    if (%.mp3 = suffix? file) [append waves file]
]
append waves "(CHANGE FOLDER...)"
initialized: Mp3_Initialize
view center-face layout [
    vh2 "Click a File to Play:"
    file-list: text-list data waves [
        if value = "(CHANGE FOLDER...)" [
            new-dir: request-dir
            if new-dir = none [break]
            change-dir new-dir
            waves: copy []
            foreach file read %. [
                if (%.mp3 = suffix? file) [append waves file]
            ]
            append waves "(CHANGE FOLDER...)"
            file-list/data: waves
            show file-list
            break
        ]
        Mp3_GetStatus initialized status
        if (status/fPlay = 0) [
            file: rejoin [to-local-file what-dir "\" value]
            Mp3_OpenFile initialized file 1000 0 0
            Mp3_Play initialized
        ]
    ]
    across
    tabs 40
    text "Seek:   " 
    tab slider 140x15 [
        plength: make struct! Mp3_Time compose [0 0 0 0 0 0 0 0]
        Mp3_GetSongLength initialized plength
        location: to-integer (value * plength/sec)
        ptime: make struct! Mp3_Time compose [0 (location) 0 0 0 0 0 0]
        Mp3_Seek initialized TIME_FORMAT_SEC ptime SONG_BEGIN
        Mp3_Play initialized
    ]
    return
    text "Volume: " 
    tab slider 140x15 [
        volume: to-integer value * 100
        Mp3_SetVolume initialized volume volume
    ]
    return
    btn "Reverse" [
        Mp3_GetStatus initialized status
        either (status/fReverse > 0) [
            Mp3_ReverseMode initialized 0
        ] [
            Mp3_ReverseMode initialized 1
        ]
    ]
    btn "Vocal-Cut" [
        Mp3_GetStatus initialized status
        either (status/fVocalCut > 0) [
            Mp3_VocalCut initialized 0
        ] [
            Mp3_VocalCut initialized 1
        ]
    ]
    return
    tabs 50
    text "Loop Start:" 
    tab start-slider: slider 120x15 []
    return
    text "Loop End:  " 
    tab end-slider: slider 120x15 []
    return
    btn "Play Loop" [
        plength: make struct! Mp3_Time compose [0 0 0 0 0 0 0 0]
        Mp3_GetSongLength initialized plength
        s-loc: to-integer (start-slider/data * plength/sec)
        pStartTime: make struct! Mp3_Time compose [0 (s-loc) 0 0 0 0 0 0]
        end-loc: to-integer (end-slider/data * plength/sec)
        pEndTime: make struct! Mp3_Time compose [0 (end-loc) 0 0 0 0 0 0]
        ; TIME_FORMAT_SEC: 2
        Mp3_PlayLoop initialized 2 pStartTime 2 pEndTime 1000  ; 1000x
    ]
    btn 58 "Stop" [
        Mp3_GetStatus initialized status
        if (status/fPlay > 0) [Mp3_Stop initialized]
    ]
]
Mp3_Destroy initialized
free lib

7.15 Case 15 - Creating the REBOL "Demo"

The "paint" program was covered in the section of the tutorial about the draw dialect:

view center-face layout [
    s: area black 650x350 feel [
        engage: func [f a e] [
            if a = 'over [
                append s/effect/draw e/offset
                show s
            ]
            if a = 'up [append s/effect/draw 'line]
        ]
    ] effect [draw [line]]
    b: btn "Save" [
        save/png %a.png to-image s 
        alert "Saved 'a.png'"
    ]
    btn "Clear" [
        s/effect/draw: copy [line]
        show s]
    ]
]

The "game" is the obfuscated snake program covered earlier:

do[p: :append u: :reduce k: :pick r: :random y: :layout q: 'image z: :if
g: :to-image v: :length? x: does[alert join{SCORE: }[v b]quit]s: g y/tight
[btn red 10x10]o: g 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)]view center-face
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: copy/part b 5 p n(b/6 + d)for i 7(v b)1 [either(type?(k b i)=
pair!)[p n k b(i - 3)][p n k b i]]z w = 1[clear(back tail n)p n(last b)w:
0]b: copy n show c]]]do[focus c]]]

The "puzzle" is the tile program explained in the first section of the tutorial about GUIs:

alert {Arrange tiles alphabetically:}
view center-face layout [
    origin 0x0 space 0x0 across
    style p button 60x60 [
        if not find [0x60 60x0 0x-60 -60x0] face/offset - x/offset [exit]
        temp: face/offset face/offset: x/offset x/offset: temp
    ]
    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]
]

The "calendar" is a simple application in which the user selects a day using the date requester function. Events for the day are typed into an area widget and then appended to a text file. The text file is searched every time a date is chosen. If the chosen date is found, the events for that day are shown in the area widget, which can be edited and saved back to the text file:

do the-calendar: [
    if not (exists? %s) [write %s ""]
    the-date: request-date
    view center-face layout [
        h5 to-string the-date 
        aa: area to-string select to-block (
            find/last (to-block read %s) the-date
        ) the-date 
        btn "Save" [
            write/append %s rejoin [the-date " {" aa/text "} " ]
            unview 
            do the-calendar
        ]
    ]
]

The "video" program was covered in the section of the tutorial about multitasking. All it does is continually load and display images from a web cam server. The image refresh is handled using a feel-engage loop, which checks for a timer event:

video-address: to-url request-text/title/default "URL:" trim {
    http://tinyurl.com/m54ltm}
view center-face layout [
    image load video-address 640x480 rate 0 feel [
        engage: func [f a e] [
            if a = 'time [
                f/image: load video-address 
                show f
            ]
        ]
    ]
 ]

The "IP" program was covered in the section about the REBOL parse dialect. This program reads a web page which displays the remote WAN IP address of the user's computer in the title tag, then parses out all the extra text and displays the IP address, along with the user's local IP address (the local address is gotten by using REBOL's built in dns:// protocol:

parse read to-url "http://guitarz.org/ip.cgi" [
    thru <title> copy my to </title>
]
i: last parse my none
alert to-string rejoin [
    "WAN: " i " -- LAN: " read join dns:// read dns://
]

The "email" program is extremely simple. The user enters email account information into a GUI text field, and then the mail from that account is read using REBOL's native POP protocol. The contents of the mailbox are displayed in REBOL's built-in text editor, each separated by 6 newlines:

view center-face layout [
    email-login: field "pop://user:pass@site.com"    
    btn "Read" [
        my-mail: copy []
        foreach i (read to-url email-login/text) [
            append my-mail join i "^/^/^/^/^/^/"
            editor my-mail
        ]
    ]
]

The "days between" program was covered in an earlier case study. Here's a simple version of the program (an example given in the first part of the case study):

view center-face layout [
    btn "Start" [sd: request-date]
    btn "End" [
        ed: request-date
        db/text: to-string (ed - sd)
        show db
    ]
    text "Days Between:"
    db: field
]

The "sounds" program was also covered earlier:

play-sound: func [sound-file] [
    wait 0 ring: load sound-file 
    wait-flag: 1 
    sound-port: open sound:// 
    insert sound-port ring 
    wait sound-port 
    close sound-port 
    wait-flag: 0
]
wait-flag: 0 
change-dir %/c/Windows/media 
do get-waves: [
    waves-list: copy []
    foreach i read %. [
        if %.wav = suffix? i [
            append waves-list i
        ]
    ]
]
view center-face layout [
    waves-gui-list: text-list data waves-list [
        if wait-flag <> 1 [
            if error? try [play-sound value] [ 
                alert "Error"
                close sound-port
                wait-flag: 0
            ]
        ]
    ]
    btn "Dir" [
        change-dir request-dir 
        do get-waves
        waves-gui-list/data: waves-list
        show waves-gui-list
    ]
]

The "FTP" program is a stripped down version of the "FTP Tool" explained earlier:

view center-face layout [
    px: field "ftp://user:pass@site.com/folder/" [
        either dir? to-url value [
            f/data: sort read to-url value 
            show f
        ][
            editor to-url value
        ]
    ]
    f: text-list [
        editor to-url join px/text value
    ]
    btn "?" [
        alert {
            Type a URL path to browse (nonexistent files are created).
            Click files to edit.
        }
    ]
]

I enclosed all of those examples in a simple GUI, with buttons to run each program:

REBOL [title: "Demo"]

view layout [
    style h btn 150 
    h "Paint" [
        ; code for the paint program goes here
    ]
    h "Game" [
        ; code for the game program goes here
    ]
    h "Puzzle" [
        ; code for the puzzle program goes here
    ]
    h "Calendar" [
        ; code for the calendar program goes here
    ]
    h "Video" [
        ; code for the video program goes here
    ]
    h "IPs" [
        ; code for the IP program goes here
    ]
    h "Email" [
        ; code for the email program goes here
    ]
    h "Days" [
        ; code for the days-between program goes here
    ]
    h "Sounds" [
        ; code for the sound program goes here
    ]
    h "FTP" [
        ; code for the FTP program goes here
    ]
]

To make the demo as compact as possible, I used the same techniques as in the obfuscated snake program (from earlier in the tutorial). Here are the global functions that I renamed with shorter word labels:

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

I also renamed other functions within their local contexts. In the following code, the value "s/effect/draw" is assigned the variable "pk". That saves having to write "s/effect/draw" again. That value does not exist in the global context, so that variable must be assigned locally. This type of shortened local variable assignment occurs several times throughout the demo code:

view layout [
    s: area black 650x350 feel [
        engage: func [f a e] [
            if a = 'over [
                append pk: s/effect/draw e/offset 
                show s
            ]
            if a = 'up [append pk 'line]
        ]
    ] effect [
        draw [line]
    ]
]

To finish the application, I simply removed any spaces which surrounded parentheses or brackets:

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.}]]]]

Here's a screen shot - it's less than 1/2 a page of printed code:

7.16 Case 16 - Guitar Chord Chart Printer

REBOL [title: "Guitar Chords"]

help: {
    This program creates guitar chord diagram charts for songs.  It was
    written to help students in high school jazz band quickly play all of
    the common extended, altered, and complex chord types.  It can also
    be used to create chord charts for any other type of music (with
    simpler chords):  folk, rock, blues, pop, etc.

    To select chords for your song, click the root note (letter name:  A,
    Bb, C#, etc.), and then the sonority (major, minor, 7(#5b9), etc.) of
    each chord.  The list of chords you've selected will be shown in the 
    text area below.  When you've added all the chords needed to play your
    song, click the "Create Chart" button.  Your browser will open, with a
    complete graphic rendering of all chords in your song.  You can use
    your browser's page settings to print charts at different sizes.

    Two versions of each chord are presented:  1 with the root note on the
    6th string, and another with the root note on the 5th string.  Chord
    lists can be saved and reloaded with the "Save" and "Load" buttons.
    The rendered images and the HTML that displays them are all saved to
    the "./chords" folder (a subfolder of wherever this script is run).
    You can create a zip file of all the contents of that folder to play
    your song later, upload it to a web server to share with the world,
    etc.


    -- THEORY --

    Here are the formulas and fingering patterns used to create chords in
    this program:


    6th string notes:               5th string notes:

    0  1  3  5  7  8  10  12        0  2  3  5  7  8  10  12
    E  F  G  A  B  C  D   E         A  B  C  D  E  F  G   A


    The sharp symbol ("#") moves notes UP    one fret
    The flat  symbol ("b") moves notes DOWN  one fret


    Root 6 interval shapes:         Root 5 interval shapes:     
    ___________                     ___________
    | | | | 4 |                     | | | | | |
    | 3 6 9 | 7                     | | | | | |
    1 | | | 5 1                     | | | | 1 4
    | | 7 3 | |                     | | 3 6 | |
    | 5 1 4 6 9                     5 1 4 | 9 5
    | | | | | |                     | | | 7 | |
    | | 9 | 7 |                     | | 5 1 3 6


    To create any chord, slide either shape up the fretboard until the
    number "1" is on the correct root note (i.e., for a "G" chord, slide
    the root 6 shape up to the 3rd fret, or the root 5 shape up to the
    10th fret).  Then pick out the required intervals:


    CHORD TYPE:           INTERVALS:           SYMBOLS:

    Power Chord           1    5                5
    Major Triad           1    3    5           none  (just a root noot)
    Minor Triad           1   b3    5           m, min, mi, -
    Dominant 7            1    3   (5)  b7      7
    Major 7               1    3   (5)   7      maj7, M7, (triangle) 7
    Minor 7               1   b3   (5)  b7      m7, min7, mi7, -7
    Half Diminished 7     1   b3   b5   b7      m7b5, (circle with line) 7
    Diminished 7          1   b3   b5  bb7 (6)  dim7, (circle) 7
    Augmented 7           1    3   #5   b7      7aug, 7(#5), 7(+5)


    Add these intervals to the above 7th chords to create extended chords:

    9 (is same as 2)   11 (is same as 4)   13 (is same as 6)   

    Examples:              9          =    1   3  (5)  b7    9
                           min9       =    1  b3  (5)  b7    9
                           13         =    1   3   5   b7   13
                           9(+5)      =    1   3  #5   b7    9
                           maj9(#11)  =    1   3  (5)   7    9  #11


    Here are some more common chord types:

    "sus"       =  change 3 to 4
    "sus2"      =  change 3 to 2
    "add9"      =  1 3 5 9  (same as "add2", there's no 7 in "add" chords)
    "6,  maj6"  =  1 3 5 6
    "m6, min6"  =  1 b3 5 6
    "6/9"       =  1 3 5 6 9
    11          =  1 b7 9 11
    "/"         =  Bassist plays the note after the slash


    NOTE:  When playing complex chords (jazz chords) in a band setting,
    guitarists typically SHOULD NOT PLAY THE ROOT NOTE of the chord
    (the bassist or keyboardist will play it).  In diagrams created by 
    this program, unnecessary notes are indicated by light circles, and
    required notes are indicated by dark circles.
}

root6-shapes: [
    "." "major triad, no symbol (just a root note)" [1 3 5 11 55 111]
    "m" "minor triad, min, mi, m, -" [1 b3 5 11 55 111]
    "aug" "augmented triad, aug, #5, +5" [1 3 b6 11 111]
    "dim" "diminished triad, dim, b5, -5" [1 b3 b5 11]
    "5" "power chord, 5" [1 55]
    "sus4" "sus4, sus" [1 4 5 11 55 111]
    "sus2" "sus2, 2" [1 99 5 11]
    "6" "major 6, maj6, ma6, 6" [1 3 5 6 11]
    "m6" "minor 6, min6, mi6, m6" [1 b3 5 6 11]
    "69" "major 6/9, 6/9, add6/9" [1 111 3 13 9]
    "maj7" "major 7, maj7, ma7, M7, (triangle) 7" [1 3 5 7 11 55]
    "7" "dominant 7, 7" [1 3 5 b7 11 55]
    "m7" "minor 7, min7, mi7, m7, -7" [1 b3 5 b7 11 55]
    "m7(b5)" "half diminished, min7(b5), (circle w/ line), m7(-5), -7(b5)"
        [1 b3 b5 b7 11]
    "dim7" "diminished 7, dim7, (circle) 7" [1 b3 b5 6 11]
    "7sus4" "dominant 7 sus4 (7sus4)" [1 4 5 b7 55 11]
    "7sus2" "dominant 7 sus2 (7sus2)" [1 b7 99 5 11]
    "7(b5)" "dominant 7 flat 5, 7(b5), 7(-5)" [1 3 b5 b7 11]
    "7(+5)" "augmented 7, 7(#5), 7(+5)" [1 3 b6 b7 11]
    "7(b9)" "dominant 7 flat 9, 7(b9), 7(-9)" [1 3 5 b7 b9]
    "7(+9)" "dominant 7 sharp 9, 7(#9), 7(+9)" [1 111 3 b77 b33]
    "7(b5b9)" "dominant 7 b5 b9, 7(b5b9), 7(-5-9)" [1 3 b5 b7 b9]
    "7(b5+9)" "dominant 7 b5 #9, 7(b5#9), 7(-5+9)" [1 3 b5 b7 b33]
    "7(+5b9)" "augmented 7 flat 9, aug7(b9), 7(#5b9)" [1 3 b6 b7 b9]
    "7(+5+9)" "augmented 7 sharp 9, aug7(#9), 7(#5#9)" [1 3 b6 b7 b33]
    "add9" "add9, add2" [1 3 5 999 55 11]
    "madd9" "minor add9, min add9, m add9, m add2" [1 b3 5 999 55 11]
    "maj9" "major 9, maj9, ma9, M9, (triangle) 9" [1 3 5 7 9]
    "maj9(+11)" "major 9 sharp 11, maj9(#11), M9(+11)" [1 3 7 9 b5]
    "9" "dominant 9, 9" [1 3 5 b7 9 55]
    "9sus" "dominant 9 sus4, 9sus4, 9sus" [1 4 5 b7 9 55]
    "9(+11)" "dominant 9 sharp 11, 9(#11), 9(+11)" [1 3 b7 9 b5]
    "m9" "minor 9, min9, mi9, m9, -9" [1 b3 5 b7 9 55]
    "11" "dominant 11, 11" [1 b7 99 44 11]
    "maj13" "major 13, maj13, ma13, M13, (triangle) 13" [1 3 55 7 11 13]
    "13" "dominant 13, 13" [1 3 55 b7 11 13]
    "m13" "minor 13, min13, mi13, m13, -13" [1 b3 55 b7 11 13]
]
root6-map:  [
    1 20x70 11 120x70 111 60x110 3 80x90 33 40x50 b3 80x70 5 100x70
    55 40x110 b5 100x50 7 60x90 b7 60x70 9 120x110 99 80x50 6 60x50
    13 100x110 4 80x110 44 100x30 999 60x150 b77 100x130 b33 120x130
    b9 120x90 b6 100x90 b55 40x90
]
root5-shapes: [
    "." "major triad, no symbol (just a root note)" [1 3 5 11 55]
    "m" "minor triad, min, mi, m, -" [1 b3 5 11 55]
    "aug" "augmented triad, aug, #5, +5" [1 3 b6 11 b66]
    "dim" "diminished triad, dim, b5, -5" [1 b3 b5 11]
    "5" "power chord, 5" [1 55]
    "sus4" "sus4, sus" [1 4 5 11 55]
    "sus2" "sus2, 2" [1 9 5 11 55]
    "6" "major 6, maj6, ma6, 6" [1 3 55 13 11]
    "m6" "minor 6, min6, mi6, m6" [1 b3 55 13 11]
    "69" "major 6/9, 6/9, add6/9" [1 33 6 9 5]
    "maj7" "major 7, maj7, ma7, M7, (triangle) 7" [1 3 5 7 55]
    "7" "dominant 7, 7" [1 3 5 b7 55]
    "m7" "minor 7, min7, mi7, m7, -7" [1 b3 5 b7 55]
    "m7(b5)" "half diminished, min7(b5), (circle w/ line), m7(-5), -7(b5)"
        [1 b3 b5 b7 b55]
    "dim7" "diminished 7, dim7, (circle) 7" [1 b33 b5 6 111]
    "7sus4" "dominant 7 sus4, 7sus4" [1 4 5 b7 55]
    "7sus2" "dominant 7 sus2, 7sus2" [1 9 5 b7 55]
    "7(b5)" "dominant 7 flat 5, 7(b5), 7(-5)" [1 33 b5 b7 111]
    "7(+5)" "augmented 7, 7(#5), 7(+5)" [1 33 b6 b7 111]
    "7(b9)" "dominant 7 flat 9, 7(b9), 7(-9)" [1 33 5 b7 b9]
    "7(+9)" "dominant 7 sharp 9, 7(#9), 7(+9)" [1 33 b7 b3]
    "7(b5b9)" "dominant 7 b5 b9, 7(b5b9), 7(-5-9)" [1 33 b5 b7 b9]
    "7(b5+9)" "dominant 7 b5 #9, 7(b5#9), 7(-5+9)" [1 33 b5 b7 b3]
    "7(+5b9)" "augmented 7 flat 9, aug7(b9), 7(#5b9)" [1 33 b6 b7 b9]
    "7(+5+9)" "augmented 7 sharp 9, aug7(#9), 7(#5#9)" [1 33 b7 b3 b6]
    "add9" "major add9, add9, add2" [1 3 5 99 55]
    "madd9" "minor add9, min add9, m add9, m add2" [1 b3 5 99 55]
    "maj9" "major 7, maj9, ma9, M9, (triangle) 9" [1 33 5 7 9]
    "maj9(+11)" "major 9 sharp 11, maj9(#11), M9(+11)" [1 33 b5 7 9]
    "9" "dominant 9, 9" [1 33 5 b7 9]
    "9sus" "dominant 9 sus4, 9sus4, 9sus" [1 44 5 b7 9]
    "9(+11)" "dominant 9 sharp 11, 9(#11), 9(+11)" [1 33 b5 b7 9]
    "m9" "minor 9, min9, mi9, m9, -9" [1 b33 5 b7 9]
    "11" "dominant 11, 11" [1 b7 9 44 444]
    "maj13" "major 13, maj13, ma13, M13, (triangle) 13" [1 3 55 7 13]
    "13" "dominant 13, 13" [1 3 55 b7 13]
    "m13" "minor 13, min13, mi13, m13, -13" [1 b3 55 b7 13]
]
root5-map:  [
    1 40x70 11 80x110 111 100x30 3 100x110 33 60x50 b33 60x30 5 120x70
    55 60x110 b5 120x50 7 80x90 b7 80x70 9 100x70 6 80x50 13 120x110
    4 100x130 44 60x70 444 120x30 99 80x150 b3 100x90 b9 100x50 b6 120x90
    b66 60x130 b55 60x90
]
root6-notes:  [
    "e" {12} "f" {1} "f#" {2} "gb" {2} "g" {3} "g#" {4} "ab" {4}
    "a" {5} "a#" {6} "bb" {6} "b" {7} "c" {8} "c#" {9} "db" {9} "d" {10}
    "d#" {11} "eb" {11}
]
root5-notes: [
    "a" {12} "a#" {1} "bb" {1} "b" {2} "c" {3} "c#" {4} "db" {4}
    "d" {5} "d#" {6} "eb" {6} "e" {7} "f" {8} "f#" {9} "gb" {9} "g" {10}
    "g#" {11} "ab" {11}
]

f: copy []
for n 20 160 20 [append f reduce ['line (as-pair 20 n) (as-pair 120 n)]]
for n 20 120 20 [append f reduce ['line (as-pair n 20) (as-pair n 160)]]
fretboard: to-image layout/tight [box white 150x180 effect [draw f]]
; spacer: to-image layout/tight [box white 20x20]

view center-face layout [
    across
    t1: text-list 60x270 data [
        "E" "F" "F#" "Gb" "G" "G#" "Ab" "A" "A#" "Bb" "B" "C" "C#" "Db"
        "D" "D#" "Eb"
    ]
    t2: text-list 330x270 data extract/index root6-shapes 3 2 [
        either empty? a/text [
            a/text: rejoin [
                copy t1/picked " "
                pick root6-shapes ((index? find root6-shapes value) - 1)
            ]
        ] [
            a/text: rejoin [
                a/text newline copy t1/picked " " 
                pick root6-shapes ((index? find root6-shapes value) - 1)
            ]
        ]
        show a
    ]
    return
    a: area
    return
    btn "Create Chart" [if error? try [
        make-dir %chords
        delete/any %chords/*.*
        ; save/bmp %./chords/spacer.bmp spacer
        html: copy "<html><body bgcolor=#ffffffff>"
        foreach [root spacer1 spacer2 type] (parse/all form a/text " ") [
            diagram: copy [image fretboard]
            diagram2: copy [image fretboard]
            root1: copy root
            foreach itvl (third find root6-shapes type) [
                either find [1 55] itvl [
                    append diagram reduce [
                        'fill-pen white 'circle (select root6-map itvl) 5
                    ]
                ] [
                    append diagram reduce [
                        'fill-pen black 'circle (select root6-map itvl) 5
                    ]
                ]
            ]
            append diagram reduce ['text (trim/all join root1 type) 20x0]
            append diagram reduce [
                'text 
                trim/all to-string (
                    select root6-notes trim/all to-string root1
                )
                130x65
            ]
            save/png
                to-file trim/all rejoin [
                    %./chords/ (replace/all root1 {#} {sharp}) type ".png"
                ]
                to-image layout/tight [
                box white 150x180 effect [draw diagram]
            ]
            append html rejoin [
                {<img src="./} 
                trim/all rejoin [
                    replace/all copy root1 {#} {sharp} type ".png"
                ]
                {">}
            ]

            foreach itvl (third find root5-shapes type) [
                either find [1] itvl [
                    append diagram2 reduce [
                        'fill-pen white 'circle (select root5-map itvl) 5
                    ]
                ] [
                    append diagram2 reduce [
                        'fill-pen black 'circle (select root5-map itvl) 5
                    ]
                ]
            ]
            append diagram2 reduce ['text (trim/all join root type) 20x0]
            append diagram2 reduce [
                'text 
                trim/all to-string (
                    select root5-notes trim/all to-string root
                )
                130x65
            ]
            save/png 
                to-file trim/all rejoin [
                    %./chords/ (replace/all root {#} {sharp}) 
                    type "5th.png"
                ]
                to-image layout/tight [
                box white 150x180 effect [draw diagram2]
            ]
            append html rejoin [
                {<img src="./} (trim/all rejoin [
                    replace/all root {#} {sharp} type "5th.png"
                ]) {">}
                ; {<img src="./spacer.bmp">}
            ]
        ]
        append html [</body></html>]
        write %./chords/chords.html trim/auto html
        browse %./chords/chords.html 
    ] [alert "Error - please remove improper chord labels."]]
    btn "Save" [
        savefile: to-file request-file/file/save %/c/mysong.txt
        if exists? savefile [
            alert "Please choose a file name that does not already exist."
            return
        ]
        if error? try [save savefile a/text] [alert "File not saved"]
    ]
    btn "Load" [
        if error? try [
            a/text: load to-file request-file/file %/c/mysong.txt
            show a
        ] []
    ]
    btn "Create Zip" [
        if not exists? %chords/ [alert "Create A Chart First" return]
        ; rebzip by Vincent Ecuyer:
        do to-string to-binary decompress 64#{
        eJztW+uP20hy/+6/oldGsJ7bcEU2X00Zd4bX9iILXO4AY5N8EOYAjkTNMNaQOomy
        PTbmf8+vqptkU3xIM4cgARICHmvYVdX1fnRrPn745a9/FsvrFy9W1VfnW75biFVZ
        VNnXSixfCDyr/crZlsXtwvzeeVz885IkSsJEJYEQju96bhCHrhKOF8s4CGToSgKS
        QeQHnh/jo1KRG8aRFzb0HD9O/CCMPA+fvciPwziUEX4RMkhkpEAIH90gAGFf0j6h
        lCqUbhTRPkEcY3dftvx5kUwCMBYSCU+GIO1KIh64sR+5oAo8FUWhBDnwJIJEhX4U
        Bgq4IJbIOIqilj/phknoRrSMz1GcuJGrSC5wBCRFvHoKLISQzSchVOwr0PMJHjyr
        JArjlj8CVH6SsP4iBQFiLyR6TuCrIJRhABJJ7LsBRCQg3w8j3w8U6S+KPGI1avUn
        HNggCsKA9ZdIpUJgJ1hIkhgSu0lEJlBeEvpgFkDYzvO8JKJ9wtiVkCgMGnqe60ZK
        KS8I2XQkVBzR3k4cuxArimLCk6RJ6I32gd2TOJaQgei6RKCl50Bb0JRL8jqeD39x
        vTCCMgWwYdYgJP3BYSRkc0mIEDYIvdCnfQJP+lC+77XyRqEXwwqK9efHWPahENID
        VOdJ2A94xHMko4T2CaMwdGNJvEKqIJJebNFzlAv7h2HC+oOWwFWSkFxR7IGnhPzP
        C8mllMv+l4BXP/LZ/+BCkZcElrwiTkLlh6Ek1/IC2N93pQyJJwgC5cAXHUgVKC8G
        HxxDgUdUJHuH54VQTEPPIWbJA2lrOBR4ChLaG3huHICviA0FVv1YerRPhF29wA3I
        F32yrSv91v+kC6XFiBGXfwtJ867PsYQHvkN2dPA/wGQYUQz5McweRz4LRL8pz2/5
        g/IilYRxzC4sOSEkCcUERE1CWJhihewRKNIZ4ilCRMBTSR4J51CBp1r/U1BO7MKY
        gmMM1pCkJ0d6HqJAQgO8qx968EyyaUwBqRLONT4JLmPX0h+CN4CTUIoiwYIkSOKI
        bBrBt0PF9AQ0CgvAiwnBlyQF0Yb34iUiuPUXz4e/+WHCoSkSFSSkb9JfGMR4i1RF
        sSIhYBKHimMoAo8wNwUXtAd2PK/lLyQ4KJi3iwOIgqDlmIDh4bBuRLEC0jHUyj6n
        wlj6yqd9EBmsY7flDyaCjwQR2QMxBu1CZxxLgfQofkOOMWDCIsQTlBr6fkIJDsGF
        gEx87Nky6LnIXyCpONmQTZFWKdgD5B0w5VKyAZ4HHn1yQGTDIPDBIUUXIgRJTXmi
        5RCxhtiCY3K4wEt8BY/kaFIhJQ+OZuI2Voqjzw0SlA6XRIopuCjFWhyCHx+lIuTs
        66K6yMAnYJQkpCvgUkpAMqCADtlukfTJb8g8cF3lIUdJYdUkbAG2Ql2TAs6Yih0S
        kQXnTEhvKDsKviBp10C5LmKdc4JykbvAsUVQIANQRfRdjkCsJ+QJhIjcmniUvYEo
        FZiXigMNsuMfBbFEdkXSQ9RaHEqsgRUv4qqEihdHiUfUkQ6hfeQ7iowIe8HzyFNg
        fqRBN+RIgpeBhcAyigigGiklhx1kRe2OFZcRBEQEhmOXPikZIf0Q50IidFA3JaWi
        GC4ETwttHcLACAJPG4WqgkpYocJHeUKyIYdHrZYI5Yh6A4QatQYB9w+hQlFBeCuL
        QxSwGLHic0/hB1iOeHeH0lmAHEE6pOB2kd042FDIoE1FwQS3jVD/wsTiMIopMwfs
        bjC2knBu9nKUeKgAlYKIJFAg0iRnxQAMIF592gh5TmFjyygCFHyJxKkzMrIkJSny
        IfBBbQqXJo96IRmw90ewODZyyQ996BaeEUUWh/BYdFcel1+PyiYeyeEWI4uiDHFN
        g02gXS/k2gRXcWPuIZDEAAAci8PYRz1CeQ04DkPqtJKQnNbF5hQSFL+UPShwyFho
        xqjIUGNDXurD16SVvOC9iuwScdQgUSJsdXFC4kQajbmoUeeFxEuGjdDt+DCdx1GG
        foBSXJsaoDwXJLmYQJ9YhqOSXWNoB4mUTOJQQGMXlxtDMIi2hPIRVSrIg+xnpQbl
        I/BhK86uCFgoyKVWE0EVkOZZSPg1jImmh2IJFoQpKO84ktw+QuWykleM9EgFjgVD
        oUYUwru5PCnkyDgMOakjn8Nu3BrCi9F3UkoXAedMFNOWQ9RjlCBASM0wQiDi2ujF
        HrWHIduKOl4F0Ti/oByhpYkSTn2o2S6ylcUhmm1k/5gbYAHTkBlc0mGETgcGcqkT
        g1DcuLtc0UARfazkIKPONbECz6HyFlAAkQQOJXzEBHsekij6IF2t0PpT4eUCAGFg
        4iCmCg/fRGsCh7I6TPS/KI+xrqA+Eheyp8spCnUT/knujnQCD+Yy5lGCg4dziUQd
        hVfDjnaFgmIQooorFNIMFVD2Qm4GYBKSDGWBLBLSRtQ6QHpu19F9INTdxLdUGEgq
        skFAoSYirsKBz30bumvEleIeE70cQjIgFYZU0PyYUomA3WDAGA2bVaGgnASxy85B
        WQfr3PyFQEMB5pQBRqkwetysYSoj1H1+e1c5h7t8UzlqITbHYmUNY7OP7fJBVHeZ
        +Jxuj5m4eRBK3OR4lxZrsc+q4744iLz6edagasBljnnvNtv/cC1mvzfoVSmYpIa+
        bvfLcuyxF0V2m1b55+xNTaUzHTqe+FruzRLt71AfORf0kxawTm8pFVDH2uBen5Ah
        eALUhBi/hX3R/uQfx906rTIHE+uAkoz8pB9ApTTXWprgV8u0eHAO1T4vbkkX7+kd
        1LC6y1afDsf7Fhq4Ha39VuRVnm41lz2Fbcp9lq7uQCfdm426kzQz/KpjZXp3xZra
        5atPzRDOO5M+UGp5lWn+JLyeUprZ3ZfjykjFu4/vUEtrCZ+lkL5rZPe76uGNIfHy
        u2uexxPjbst0Lf6zzAsxe/l9JpZV6dxlX2vXaY2pCeH17HF2PSzo60N+W6QQKju8
        0KRX6dbZ5NvMwcoCTBsxxMvvCONfXEydj1pFWVHtp2ERqxo2K9ZOuXFqlGFoNK8a
        mrh21tlhtc93VbkfAcdQ8GhkWJXF52x/yMuCDaYlAUKuj19OjfiOwTm8hfFFMk4q
        tnlVQRhwm2OJkCdD/t/rcNf79wMeLNzkRbp/QBJhc3XNiGX2wjbW4ZyDECSLYfRV
        C42WU7lXJ7F9Ho2zZKQxUQn9S3HnddKJrgYzCWEc7sp99WyNM/b/PpWfS6EAfaKj
        3eS3/7CXrcuBFKDRKdw1a7dZdYFRhszAjDZs9zm0EpzYAX6KVUuXd1lKJZXCNbOM
        tSp3D/Ndim01eWmxf063A2H7P8t80LjF/WFdHpwqv88mBGCPYEhBkH1u6e2lzq+N
        bUJ2flce9+IPdACsrjpeXtZRPb/Pi2OVAciXfZDTIJgfMuy9pli46gaAlpQKz2WS
        EmRfUnr7RElptvyDeHWffhWuYfIhQ/w6NJe4o1KXRXWnhRZ1tzVfpw+WUOR7F9lv
        sy/vz1jwKf7WxV1YIazf2Dqg3eCQ6+PqtI3E0ICGvtMAwgk6IJidui2ifQ8g6DTQ
        WoUTDea+VktnbN/R0rD1/7u0RLuNaCmkQ2lbB+ROP7HrdAAD5V6qqvEu+1u+c6gD
        emjU9P1deb9D63XIKI9RJ9UZOSxmuS/TEJSFMupdVzXymhdaYNNnafB1vs9WaKMe
        BO+toa4fG+gihQ8tCZT0/Rf6rdwwaqentaLzX8t1vslXGGLQcPHSEMJ4E2zY1tBz
        lqwzINxn1V25tsRzmBxU1r455N8ycfL7qWu8RgHYlEIziaKb0TzRIIF5e9dFN7vr
        AYARG6geA4umyRTbrLit7nTr/mKyre84jhZ1IX48wEKW63ShXotv2/xGQJVZet9Z
        OdHRonnR5dxoY2MLD+VkG5gxh1v8s+D92xkvLw4V1NEhYIR5ZYt6Jf7Uvjhh5mrg
        3rNm0hTM3jo9h0/5rmd8OQ5apfm2B++oHkKj63W22cJrOwDXfV5HbNOofktl5mTf
        Lk0rFYw5Zbqpsv2wTz7R1wYT3Gs7cVjxbx5uIF9+fxywVHcQ7C3r4fQRG5jJawIC
        2r499NaNO5lg/6NR89BdeYdYAz9slQE7Gnx1gj/kBUyh96bTyHHCm9OncTjOiQy3
        HtqBchw9r02W6QMM5DX76SWiIVZMNaydhdP8a3YE/qjfT1gt+4oassmz7XoMlumw
        HDXVQd1rkKK0KA7mBaOStqhNBJSBNecJKHHP8PDTA4zzPi4O6KdXfSn7gOiq/z8g
        /s8FhGVhUAVL93Cx88Dr/PCpON7fwPiHaqgqtqA0je0LJPS0Ql91g8mt70fWuaHm
        +okYpr8sN5tD1uflSUE/EfEtkNHTaOnUP/k/aihp2zfjJ7O/f/y3D3Rw3HbTx705
        Xz78PLNHjephh3bWNI14oZvg+lQeWObjtdkcLrRLqztmoLf/92bQAeIP1OZyR23a
        +fvyMzp8UiwdYM7ms7b9NgxpaMK9ZOjJdb89fIOAxZfYAhG9yfeH+lBiaQYl6r1E
        Qd9S08J1UPXgMTjHNLwukChW5TpzGr0265xs/2k2O2GoPosAL+Crs8Rq1+ukWzGb
        XY+tIzIw5HUgOr4BZxU/YsRqZqtfjvl2TXMVvUz3q7v8c1aPoewccBJMBWh0PtUD
        zOFnTbt2JhOVWKVakcOGENCQ+rm14Rfk7Y4NhT4Z+kG0089/MAxsekNsibxqFaGr
        ShefuCK0X4krQsuL1fa4ziwGWgLzdZbt6DKFQQ5aFII8HG/q8Q/MWwjwqpsSY87s
        z/mhqhG+3JFWml60uB2c0DgBcL3maiuKG6dWjiZTIOvDP9o0Y5VpM4kyoJ7hymMl
        mqJguTje10GmHbWuj00OqNXecZglJhdEjFk7cfGTWrhkoIWYRLFjrre1IVDusmKu
        pZtry8+/7HOUPF63mv9W7oWlt4Vwx3VlIhYtzIsTh9HDHllx+Uq/ubq2XJJsuSzK
        iuejNwbnRANky4VZmnvdJWPFhY5BTofa9Plef+pGKotmgHvJPt1uxbJBNNvid3IA
        frOn2ZuJLrtkT1u+/KARUkGi8WDdnnC8GZpX9d4UIA3GG83t9QCnr0W6gzXX+ru8
        RVVncrFFoAwWsfqSkqEaOUbaNnqMu/HYakKfMOaL7iHOoAIGujnbj6xYtK8260cf
        0RXllxcDcpNmqSyUhQlvU0OJtzdDpDDItyEpltrNmISRqg0X42FypBk1ZEzV2qYo
        FFqHteuP4LE2N2zX2l0n9N5qgA8tGZq3+fHeOtByBjvU+un3wvVDljfBL0a7oj76
        iGS6ctdxbin1gu5cR5fdrQwzBM3VZWC5Q5mqBqKantekr9PDRvjX4MGi9ZijzubU
        s6kcWX+y0/vopKlrM3efVrXrzwR3aXGb6SMgpj+XIpDtKUmbawd2Ose7HaP98mW2
        G6Db15LupnugVPY0mX6Y2lXCqpQ/NaPLEOJpomSfQcvUeT3lUC0B4m1kbu7poptx
        e9f8neXuxEPN1dRyfYiNrTpg7SRnZTvIe9P0aIT+NJQedOecbVrocw7XisXNqWmx
        piZDevrQwy0J3ZM07chqS8Fsmo4aqBW51y0fC7tffp+t7NsIu2vmi7vW/9vG2fTL
        fy22D+JLuf+EJhK5vHPMrM8XDvVxBg1E9Qms1ULTo6Xo9MFUQupe2Oqh1w2rnUaa
        Bes3091riLetTC2ZLo2p9rjFoVHuFSRJj9vq6oTA3485zSrvy+LHiluHCSKDXTYf
        EQ3chHAmGD/uoBTraLfS6badwOvXXNubg5sGs0OTYaxY4dmMnZEPr7P9vtwfTjt2
        /dZuZgl4pImnpkwriaq30fdpf16gFxnv3qlmma9AzJ7VtpsG9CReCIG4O8Xi7vEr
        mdK8OeHnPv2UUYrQwxhDnGOn9tU6Lds9hOnXB8KYNNy83qX7QzYnhmtiJ8lp6iD/
        UA42qtXd/nju/P/VeNt51QOWukqP3xVwn6ddXsMO5sRXptf7lu3LNxp8br4NKZbm
        6GKTbuFHfRZ4BxNOU1vUlz7WfbJ+NUKSA8nI90p/UaD7xQGGGEHmXqhGHuTH6le7
        B6mj0HPNxOB5bEuR6Tg0Csy/Ich6kH2Gg9qGo0e0u4deogomNN2732q+7HNKZkR7
        /fvgqe0GLm/bDXuLI1va6XXKiyy4jitZ70d26OfrqX160J3deqsTUnVEm3DIZrgY
        nyvo4TIxuNrnoS/yoMDd++3a5IOgw6wf0Jqs7uamYtfJYHxedM/Mkr2KuZgEp6f9
        ppj+JvWZSwv7YY3O/uYI50+i/ISaQd3U9d9OzlPtZ3xWVeen5Ha419m2H2zTJGpp
        +eBqCmhitK+fM9/pHHteflf014sfgviRmvD37lvvrfuWGvHmO88X0jGXI+8J97d/
        ef9xqnE/fWaEMAMiXTcsxsbBoQfzcL5GNPyxo3xx1lPqx3wt9YLLsaGnkdrTd0L0
        HXuw4qGbHagXIxQU4d7kcPdsd6G+aN9HnqS3GDNIadj1dp8+HNCPXM76ozWMt1PI
        k9CR4Sr7PrYYKpRTBIpS39dt04FzmzHE9h6uaP9e4EJ/eYJb1s5B33aLOt8lu9xF
        Zr+9f/t769qDhzpDz8vvsUrePdZfa3pSSLhsiNo2dM8Hvyie4pOuoD+OFL9++PVX
        AUVfhPYP5G175+fbtov9pPTz4S/vWxtlxeUx8Dxux6uOXp1crr8vR5PkG0Fnhktd
        i/gvXujjBeXCrpSbFG3Kms7/+cBhsl42TJg5Vv8/eJB++vRbgfPZ4gJJntFh0EOT
        aHMD+fTsv892WVqJ/Fk1nx778LQnwkUU6Kn/EIP/jIt/6G8mXmCOaR/ULHa7qUv9
        Y5zy8MrUxQWxsDzxU1NsZnXVmRFH45s+wVWf4qLDG1r3r3wkN3wBaz9D96uT3xgZ
        QrBPjCZtc3p9NJ1p9D3g5e55rU+jlj2MJxv+tQ7Q9jg15b+oQClr7z/GLT52VTY9
        PGz6x1fzxQWI9AwcbGnc50bK9JZ0Mr1sDhyvxWG3zSu+yzpv1WE5GfeZchLuM+U0
        DPUPVs+zwo5QnwReqvH6eXrKPTT3oU/zDHp6d6eL8fMp+znXJzxtpf+2e87QXa/K
        TiNknc6anHxSBfS3cObvc+TYY2EuRdYLpGnr5LOP1V6o0ARS5+pZna07CC0Pet62
        j9hfoGv6L0cgfyluTAAA
    }
    zipfile: to-file request-file/file/save %/c/mysong.zip
    if exists? zipfile [
        alert "Please choose a file name that does not already exist." 
        return
    ]
    zip/deep zipfile %chords/
    ]
    btn "Help" [editor help]
]

7.17 Case 17 - Web Site Content Management System (CMS), Sitebuilder.cgi

write %sitebuilder.cgi to-binary decompress 64#{
    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
    hI55M8U/gP7vhgF/Hgc8NHh8fLv6VP6ffvPot5Dvl+4/Yb/vRO0vPxGutjlU9ivk
    /EXP3yR96vn7rtf+wXaG0e/HA0Cn8cd36vUr2sU/IfFPk/8FzH6Hez/R0t8gya9f
    P59+J8af+s5/xoy/wa19XLTHkL8h4j8rWPXH7JO/LMcu/SHK+49fmvz128b8z1ji
    335DX/+T8f962vHvhO5XCvertXz9Hc3jnux3mlRP1ZgfVjqCv1CAr78KNL8J+Ljl
    1cFXTvQqOW59/Q05+06zuuxIpL/pf+ry80b8Q0b2faTPrv/3t/8+/ucttm+nw0rj
    H6tj8z6nedDFzwUMn1b78eu+/ET3fqe876WW/wIb/viHi8zyKDrs9NsSf+FsPy3x
    b2ce9p+o6bcSfmF2/0AC+l+S8Ks6fpYRR5+e/EtH9lvk+njGy0HD02OX/6HIf8jE
    v5vA7FfT8eU/2/T/Vxr+09c//X5PToZ/zGo4aHHbj99zxz/98Xs954hFfxiydvnD
    MCVJvh5t/vxPf/ws3R0tii49PtMjGPzTH7vm8zqou+Ozzz8/j7zzeT/6fPoZSY/r
    7PP+/u3peCz1+Ow+5fSfV+n+fSZ9XLdz/IfYD7Pv4ebb1D7+/I1i/910Pgs9nxf/
    8a3Zv/4XRBxE83vZ6vujr+DXv3xGpEPy/3Eo/xD9ccSVg1y3fR4Pv9PBNzv/1MAv
    tc6fI9uxgmTs3lM7+t/EfZYM/I/k2O/2W22hjaKPb70//r8s8nuPz2e/DU9ff8i+
    5yDuMLIP7jN6fi5H/baFP+enr986HNHm78b4XU7/u2D3PeD9L8eFTw/4588F/Pi3
    k+jf6f9wr7/93b1vMeSfm2Do/v2nPycRfzsU/LnGXwX/5e9i+F9/VsSnsv52evC/
    vLTPSmrz6/p+SW5fv+/IZ9X6Q/a7b6v4Hmf/N0/gW5fPvl//xHxefhvyPxnu5/39
    7bB/+/iXX1We5P0wfnzLQL/m6X/9+Os3L//6ceT9NB5//L+Cym/KP1n5YdLfyj6f
    5vbbIf/yv1nHB0hrj8j3iYY+L/7/U2beHIqYwm+17a9/En7z7XPMv/2dTf1Xsddv
    y5hHiPxc0DfJcfTxaTsfVd6U/6DW/Q/R1bd0ciqk/YSsjm38tMFfdvkXYPSrAPIv
    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
    0JLKjHWaW0qqN8dJfzvRLcsaORVOFLgVrkIcI7NiZ37SJLtxicdu3KOuT1+Xttqt
    nN75vDfk0Fgmx9kw7k77Y9sbjrrreNBzzGlj28DHdR4sgaketRWLx40qESiTxtjA
    92X/jBZYuhULbHy0KirdYKRuRWYZw6I1rkKL54rEuZPmXL/2N4bWUUYn5ug2ieow
    YXSSUFW6nFrFyXPS4NuLRn29EJaXQ7VW9UbN4XpXlyAaqUNsuImVa2PRaVkdlhwo
    sb5oJM36YL2MVztuV3ZDB06y3s/NmcJRjaRrtntTAJ2ZfZpoHjdRxkmjO6nZp+3S
    31Z9Hvdk8hxLbWlL8lRfulzd8vYnbR1u5322xiYn+6xGQle9nuLeZLm/tncn7cIc
    NkDxniQtqZagmDW6Oxa6s5npJIRxWnvVcgp/7qw39UHfWdk252/IxoLvVSZii/DX
    W9pgmaVaqQ2C1nm4XA6M1q7BLGnd6YW0Jg8Xijetlq2TLc+qlUF9Z1y8aofrypWF
    AW5xtPW9SX1mrI+iznhD2vKsrVodd/3ZWhjbo+bVq8YBTVTjcjqab240RdBXzMWR
    vfaZDPc7sx/Mtb67q68tb1FnTvth5ert18cNce32JsR8yS0c4Ro41ejUfrru+dQT
    FqttrbsfB/toJzZGicuMq+OR0Zka5GRgVzdqdzEeervzdm00+Etyji+zylhdyVrd
    fNrnyLYu3mg0PgzEPXviuNqkYw81rtveH6L+dT3QuYEa+c0B3pkvq7XRaTY5u4d6
    axiS7mG8oJ6+qWUYHt2Z3DivB4bbqg6Xuz4nLdtsfDrO5IAnnPph5LoBbW6smUSQ
    4+5JlunAncpjQb9W2kF5TTisn7bkrhMNvJhtD4h4siKD+ah7Ydr9yzlcj/f0aSA5
    wWiBq42dflyM6dbkutgkk4Z5rS03ZYtux4f+gJyZhy1uNuWx4u27s3NzUx8PRp7b
    aWjaNXKj6aE6PWwsvMlNrZ1ypNa7q0nLjUmv+nRXBFllGW/AnZc14hhRRNhfUB2F
    HXOXPd/vKMcmvx73lRj3prEcdWu25x/GZizi3cp5xOv18orYpr7y955wCJP2eh+3
    CL13xuN43AlbU47b9tnG2hqH26DTb0U6u5hK9Ixd9PazDrMcd8fjXnmLBYtvom6w
    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
}

editor %sitebuilder.cgi

Here's a little script that I use to create quick header images to get generic sites started. To use in the standard templates in sitebuilder.cgi, convert the image to .jpg, or rename the header.jpg image reference in the templates to header.png:

REBOL []

save/png %rebol.png to-image layout/tight [
    origin 0x0 space 0x0
    image logo.gif
    image logo.gif
    image logo.gif
]

save/png %header.png to-image layout/tight [
    origin 0x0 space 0x0 across

    ; Load your own image here:

    image  load %rebol.png

    ; Use your own text here:

    box black 400x72 font-color white "Sitebuilder Demo" effect [
        gradient 1x0 tan brown
    ]
    box black 50x72 effect [
        gradient 1x0 brown black
    ]
]

view layout [image %header.png]

7.18 Case 18 - A GUI Playing Card Framework (Creating a Freecell Clone)

Card Images (save as %cards.r):

REBOL []

cards:  [
    64#{
    eJzt1z0WwiAMAODoc1Wu0OfkOVycvIOncOZoHTwQk2sMBAehL8mzqTqYFlH6Pegj
    2J/j+b6FElcqByonKhcqK9iU9kjHbzsurxHLDjFylTf6Mo4j1bkFyw6IXOUtN9HH
    vu2qi/UwoBZpCKpBcDDBxyTwMZCChyEBquH8iSanK2iGh5NMyp3AfPMccb4x5QIM
    ufAxkECfQwB9Dn0MHQ1q3t3WfB3xb75joGvqTUmjaEiEVrUG8rJqGpufqd4jPmGQ
    iXg+1FHeUDSmOUzt2SxonHI6FX/zW6bP4luGL/iiSf0fajFTb4iymVjlyxnLPGth
    M/VBaLapD2aK6S6AvZm44vSmDCcbVFJqNk5rnh/sPYwSJmN5J7K8Wz0AAI/VC/YN
    AAA=
    } "ace of clubs" 1 "black" 20x20
    64#{
    eJztl7FRxTAMhgVHC14hR8UcNFTswBTUGS0FA6miNbKd49nSn0g5KC1Hzy/yF1vy
    +SLl9f37kap8ir6Ivol+iN7RQ7WvMv711HSUtV60rq0rTf5s2yZ9seR6Uc6tK62Y
    5OdZT2XkflmyJ7wkl8n0d4ZrDOeMuJxcJnOA8f2pw67PkBkt5c4wNdpxIsPUaDuf
    UeyaQbErBu7h6A9kKJWmbXoWojEyw+xHL92e8RkCexhhxB/uI0OM3HNnIyZ4fjpL
    i/J8D48YxbuMjCb/zB+cw8vMvuJkJjOZyUzmCsP0L0x74Z8yDLKsw7Dl7Tww67WE
    eHsG5M89sf6uxGY1xejcfYnhPp8fMJ1EapKj2macG+4hqq4sk9lnks+wKhqRP6D6
    tEzkrIYKZHYZijA2WsOAaIE/joSYyDdR5NvqB5uyj432DQAA
    } "2 of clubs" 2 "black" 100x20
    64#{
    eJztlzF2wyAMhpW+ri1X8OuUc3TJ1Dv0FJ19NA89kKasREDei4V+R4obv3QImNiR
    P7AEQsDn1/GNavqRspdykPItZUevVT7K+9/3VnQa60Xj2G4ly8M0TXIvklwvyrnd
    Si4i+fnomzLpZRiyl3hILpPp74yok+7BcGKXyeTrIw25DNd+N4ySEGRqTaWOZaq1
    NzLI9o6Bfaj1gXZRKrmX9a0QqZYsc3a9dKnjM/VxBSP68NwyxMh/nsmICfrPTNKs
    vN6HS0zHu4y8TQGfD/hzhDl/8ck8hOF+5rixBUq0P0OmnxeI6efXlkwkbkTiT6wP
    jTYbMncaU5SezP9i2Iz0KqYF/KsMg9niMNAP+3agH7YF8VIHxha1ni/EFrVWL8SE
    CMPz9Xzb2AJ2RYYBuyvLZHaZfDOD9WFdE44pqGm/5SBtLLx9dmoHEo+x1q5i3BRi
    ImeiyNnqBBVpT9z2DQAA
    } "3 of clubs" 3 "black" 180x20
    64#{
    eJztlz1WwzAMgAWPFXyFPCbOwcLEHTgFc46WgQNpYhWSVce/WCp0tB3Xrvw1kiyn
    cl7fvx8hlk9uL9zeuH1wu4OHKN95/utJW132eMG+ayeVB8dxcC8SihcQaSdVRPzx
    3N6qK/fbRlbBLZgMwf8ZDA4GPExwMOCwhwGTYYtKphxHY0UVVEzUffJxomMA8hcE
    /UG7Pn9loFnDgT0tA0FqwqV2/qJuqFNtoTkPCvmFhzamYwaqmHoYliNme0LQ6Xpv
    QEDKfpEEk8S9MI+p6pyvYc/0xcOQmG7vQ9dzYTMXjYtZzGIWs5hrGISbMPqHP2Ww
    yLUmM8rv3X36c0u2JybEYa7MfqWcN8i5NTPO3VcxmsjmDBXyZM/wTGKcbfDU8Nsa
    nszorJV1Ad2AweSOFdPGxzamCOTZhzaD812YYmoznmfHYbNZXIznncjzbvUDyCYa
    mfYNAAA=
    } "4 of clubs" 4 "black" 260x20
    64#{
    eJztlzF2wyAMhtW+rg1X8OvUc3Tp1Dv0FJ19NA85kKauVEIGDFKAuM5rhwgTiPwF
    fsBG5O3j+xmCfVF+pfxO+ZPyAzwF/0z3zyfJpc3hgnmWghNVlmWhkj0+XOC9FJzY
    RR8vdVPKHqfJ9wwn12U8/J4hOe4IhhQfwuAePds6grgqPwJsG3AWA5C/IMgPoNK8
    m6k0W3qCLzPgOEWckxovygOVut30nCsb/8rDOk0dJjiuYsiPmPU4J7cLhmro87i8
    Yyk8vM6aSp/tOdSMthHGs/SBZ7XSuZNZe7wzf8OAcmkGofbUTHySW0x6Iy4z+c26
    PVPtGDZT7jw2MzSHWKu5IXPQmlp2Z/4Xo1dxFyMbfpNB/UJdZqz4rtoxzi1JTwiI
    ZqzM44oxz4i5JWPH7qsYCWRtxm/8UY95JmmfbeJoGnOYzljWWSsxfC47gPEr09IT
    meaa6l2pNGGaiGju2CgzpqfLdG2I6Sqm6VR/05Sd4Acse9Xu9g0AAA==
    } "5 of clubs" 5 "black" 340x20
    64#{
    eJztlj1WwzAMgAWPFXyFPCbOwcLEHTgFc46WgQNpYjWS5dS/SHo07WOoHDep/MWS
    bVnO6/v3IyT5pPpC9Y3qB9U7eEj6ldq/nqS2sqYL1lVuXOhh2za6syamC2KUGxdW
    0c9z39Ug98sSLcElmEyE8xnkdpMBhy0CTAZNhmyZDPRM/Ywgqs5WGkPpIMwYgPIH
    QV44jOl8nvnTzTMELjvOZRgvSkCdzFaWy0OlzzzkaTIYaGLDw5AesfgTgjS3MQYB
    YxlXDOwKD89YU7Gpz+HIjOJhIrvuiNXOzz8y2eKNuTIz24NDP5Pc0uln8dwx833R
    MvP9dRnGkzc8+cc3h7N8eCnmoDX9XW7M/2Lq9TuDkYSvMlhvJYtR4rD0o8ShHIhG
    btnPPC235BNYzQkeRg6yq+SWfTTaXt571XJC+i47kFH9yYy6pvU7MxFGRcSWIfan
    SzPPGhM9jMMfmzFHdYo4XX4AibGWIfYNAAA=
    }  "6 of clubs" 6 "black" 420x20
    64#{
    eJztlzF2wyAMQNW+ri1X8OvUc2Tp1Dv0FJ19NA85kKauVAJiQMigpHamCBOI+AaB
    QDyfvn5fIcgP5Q/Kn5S/KT/BS9DP1H5+i7mWOTwwz7HgRJVlWahkjQ8PeB8LTqyi
    n3fZVSPP0+RHgpMbMh7+z5A5bhfG45BBN7bHwngQTFlHUBkEKPjUIBiA/AchvcAG
    HcCo9tQMOE4XnFMzX4wbah22GDlXCn3iIS3TgAmKqxjSI2Z7nIvNIOaFPs/LOzaF
    p+f6Po1j9tewZVqxMJ5NH+9nuQ9vZNKID+bOjHoGZT9abKn12n4WjH4uakY/X8cw
    lrhhiT+2NVTj4UHMFT6NrbpPc3dSI5na4zpTe1xlhKcOZYTHdab2uM7UK7zBoLRm
    X6a2UbenZlSfxoDfETrl7cJuM519mPvpxJZ4IQ5iy+XO68WWdAN3Y4KFiRfZfWIL
    rD1treHKdGPCXgy6sT2J6d4XWLRpEt7t7jAzA6PBmGlPy03MUEyM5ZvI8m31B3Qa
    a6P2DQAA
    } "7 of clubs" 7 "black" 500x20
    64#{
    eJzVlz12wyAMx9W+rC1X8MuUc3Tp1Dv0FJ19NA85kKasRBIkNRhLcmK7r8KECP8e
    SPD318fX5Q3EfqieqH5S/ab6Agfp7+n8+T3V0no5oO9Tw4X+DMNALfdEOSDG1HDh
    Lvo51kNN7LXromXYBZOJ8DyDkoPOUMjBZCLuxtQxIxQnmzHDeMzsVHOh7GflrMY4
    4inzQuDScn6ZpKfQcO4MZtFVjgxmMDLYEoa9MIonOyVDM4dRXsnh9IK+p2lOfQ2n
    zNQ8TOTQHTqMjuvLZvKMi5iW5mumpbGKaWp1I8YRz7aa34SZ0XydV0Pzk/VZaU/n
    7Y8YT8x27p41dOzFztowterS/H+8ln16njUvU0zyOJNu+CqD4y22GGV97oy2PumB
    aGj+9nTVNM+epVUPg7Cb5hFu73DzGsujqlqV97L1GE886p6irp5irl2YIqE244nZ
    NBfj+SbyfFtdAaNeJZ72DQAA
    } "8 of clubs" 8 "black" 580x20
    64#{
    eJzVljtyxCAMhpVM2oQreFLtOdKkyh1yitQ+moscSFVaViDMUyB2vE4msNgr/I0R
    4kf47ePnGXz5onah9k7tk9oDPPn+lZ5/v3Ary+p/sK58c5X+bNtGd9dj/Q+s5Zur
    rosur/WrmvK4LFYruBiVsXCcQT+HMUMuG5Wx+GcMQvYwGLXPkL8zGFDOHb1dGXxR
    GKMzdZwlf2zBILgqGYlhPRnBiAwG0VWGf5nC+JfdwjjLZP4Eo2RoZJPNiw03PWVN
    ecxxDFumLTOMda6rOixieIAJI97ESJqvGVFjJSNq9SRmwp9zNX8KE2WOYTuY1p+k
    eRZptgGaNQWDO9WLIY2EGDdfy6TA1J3nMFgERvaZbFe7PmPKkp25x1TBIRBjuIef
    s7+4FnEZHaOtO3Iba4NXpKMNVatTmv+Pe3kuR3XLLINwF4YT/pBBQeVdZhCfyIzi
    wweieFamee2nq3DmFoyWx2YYhOl8eFTzGD+H+hqLmWKoVX9RGFQZjN+LfX/2WQ7X
    lMfql99m6rwuMDhW/B33sjKOTYoblytIR+ey9g0AAA==
    } "9 of clubs" 9 "black" 20x40
    64#{
    eJzFl01WhDAMgKPPrfYKPFeew40r7+ApXHM0FnOgrNzWtKG/CTSjDFOmdFK+F0IS
    0vL++fMMsX1Tf6P+Qf2L+gM8xfmZrl9euLdtjj+YZx7CQX+WZaExzPj4A+95CEeY
    otNrr0q0x2nyo4aTGzIe/s+w7bsMArgh4/B+DEJjrcpArZOFnsEYz1Y4jOl9qNjT
    xQIhHJpQGM4npwiZwTXpOiEqGzBR2TVMkFyeKULD0J0dRgugCOHxnIzp+jwI+Z7S
    h5AGpzHZNU14NAarqPlguhMMxdD1NnYMVl7aYqCNVNJ9FROdlhgEleFLgJgDJRhO
    T3DomYVNZp3ZZzCdNUbN+Za5bc7fhClpLgSZ81IY1DHBjH240+7EWGweP7vFh4ZY
    nJwbw1w15bzJh5Z30P4u79cES20x1ajimD/Ww0pvX3s1pq/hieGCX2nyvmew1S6s
    aRi5NklGrnGVPXFBVNfK8lxpdVXW3IYZ5aqFWc04IedpP5X2Q5s5TzsumeZdrsZd
    2YHMwB5mdupYZjbb6YxMc8HgoNgftl5Yvoks31a/90iSufYNAAA=
    } "10 of clubs" 10 "black" 100x40
    64#{
    eJzNVzuO2zAQnQRpZoksr7BIlXOkSZU75BSpfQS2hAtvvRVbg0W2yIGmCrAJEObN
    jLQrUVTWQBZIaMuS6afHNx/N0B8+fX9LNr7geI/jI47POF7RG5s/4Pdv136sx8He
    dDj4SV+4uL+/x1lnmr2pNT/pS6fw8a6n2ozXNzftuSE38VlMo7/HiP7+AphGF+j5
    R5idMMQlZmZjzswp5ZTJpy22Txi9lOQYTjojPUYsD5iYKFG2eY6eJY8YW75R9nF1
    i7tyNEf3GJnlAsN5MqjHlFJs/k7hPcb1PGFiC1XXWmAo6sswQkIB16Go5ic9kw7D
    KDpgXnmWmNmxvhY4FFOigHMfQ8Zjc7uYYHpWGOgRtYwUA2MM0/FgUV1YKrntZhe+
    x7iNaSikPPIzFLOrxd6HOAJsN2wxrpUP/QTHUEka/jrxLPLQP08koXImygwtHSaY
    oHwKsAOYnCkl+GGNgTH0cFZMQ4odz8ykS68wFX441lMoAleJ8fS5YZh6/Kpug0Od
    h9qQh5SouR6zvcdk6KkFtk16wkBPzieiyrMey7EhTy1w5PFMzHHEo3oQ0oQHMetD
    NuIxu2plDlwTnNn7uTI8pzxKVDjDO5tY1HBOrqdW3FBTi31MNZI5WboGpBB4YosD
    TM16qw3wiGzXIiVqcg4UWPXEuOWJlCECQUcNquV4HvHECCLkJp4CkOJywAO18C4e
    TJgdwDPyDx4vKC2kmCYmerAWFkuqS6Ok6w7WEnmoUKuaIet4TgO7EG6E0m1HKcT1
    iAc3Tz6kxsdzHvBYilINVga0vm4xluqIrfIg3VKug7UsRZEYxdINdXzkQzgxIMGU
    B+m28SFVTU44CIkKIk/bDoP8xtMC78IDrmeLoRolJUvRB+SYP0ZbHmHWVJeY9njC
    xAMvoSaO9cjEo1dhxy6U3kmPEo15EGrXk5QIelA+pOMpyGTl0WIIcdn6aej93FAG
    1I0almgQ7bnrmEot6LFwM0pTnGvxjLEyDdtLtYZfKnk1X2J+XU88AV0FTY7q4+Zg
    xnjbgDzrfdoIUYi9wzxirP2oK9COZGqWc6daYcgxzZvuvEWJGx7Zx3hbVZfuY6w9
    W2iC9Xhvlt7JVxjlQevSxLemO+0IVj5UnqvbyXZtqJ0Pp9xoP5YY36GsMLqNkdt5
    X7PF6ByjzMkd+/5Ig77GWCtj65XIDWQHcez1IJN9i4eaYNs1fGxjKm6kMPZ8isk8
    pc9Cs8T9PeQC88fxP2KIXgbz3LgIc8l/okv+W/0GSJQzj/YNAAA=
    } "jack of clubs" 11 "black" 180x40
    64#{
    eJzNVzuS5DYMhV1OUCwvrzDlaM/hZCPfwadwPEdgylKgKyBVMZnAB0K0VZ2Yfg9S
    94hU7fYEGyx71B/2mwfgAQTUf/719XeJ9Q+uz7i+4Pob1y/yW+y/4vt/P+3XuF7j
    T15f9xc+8Obt7Q2v3OnxJ73vL3xwC09/zFSX9evLS3+2/CU/xXT5ARgB4AnGI8In
    mNx/Wsw30pDPmBObe865aOO6YI63LkDtGJsxHnVAF6QDYwk0+axhDpfybqvT1pKa
    YfuUixMGz56r1EaMy7sTZww/3ojJnf70qz9eJAswcAiYTqIHht/wAzBd/EaH8uSP
    76L1rtIdjwJjs893YXdMD6fpM9S6YGiKGBgrKcd2njFaSkRPY8lGDPxxXlVLRNer
    aJl48M80rEVjT2FMQp8859SrFIK9ZBjj9n+fZg1JI4bdJDB20XDXWMuyJcC2TWDs
    2D7xUJuatVWzbSmUKE8YR0CqRWRptS4bAyoyYeBKVoVsurS2CbzD88xTa/FiKE8V
    ZWypQcMBA2mYvdSoY2FNm7GCThhphiy5iZn7UiB5alrrxNNYP6YgIo+nZHXG0Eew
    aMEzdETB6wUDTUFhatIaEYCklCZbbLhJqoEPC0k3rInHnQoCY5Rvg5hy5RFZb1oX
    kDVAEslmHiRiLUYMclqpE0S8xFWrNF2QdEmlETrrDJpeWtLg0WQICq8zT72B3hbQ
    2AYFI8oRo1oLDoItiUXRaLnobcwXgsIhZ+CksKZrL+uIYVB+A09DmqCfqGcwjzzI
    jZAHFmmyqftaR58R1Nr55WFLlFtjHeqNTuL7QlQBD/h0rHk0AMQDfQzSUULRy9lR
    CdEaj6AllIUheXnEyCH+QumAQfkg8yMm7Unk+SJPsQhq9EcsigHlk/AW7nYWUJ54
    WEA16iuxwqqKX3iwqpDMWIWJbe/Ck7QiTVHvstF0nv3hQeGJkAgeaJ6kWR+o3BJO
    MM4fwkMlS0YZzpg46nGOkTXj/GljrfLUGRs2+wHQaA+aJ5+RqsbGyr7CIAtbTZ9y
    ofTROSFrzknRr1jNU61SV2Wba3iHRKANTzxZdM2+RLvc2MnzPusGDM6O64aIkraC
    Tt737TvGgxREnviJ/afmfSo+MGz3RK4d/+50jXMlDNwxMTY48GrXGJmk2SfMAxPj
    J0ulMe5wPB2TasQIS5AYTsL6mHgjDxoAjHEAOryaMTFWs7etQCJi6NWEifEcGBrb
    MfdJPmJQiTCGuxoP/Y47gklDVDoOOd3bh65MuSBpokPHrOv3O5R3DIjgTzj9mJ4T
    5vC5waGVvbxfMXE7hWGBzg0M77Su/uy3ZRwSwDAF/d3RAZMODBK32xrrZ+f5xj3k
    GfPd9RNiRJ5i+j3E72Gerg9hPvKb6CO/rf4HC5MFI/YNAAA=
    } "queen of clubs" 12 "black" 260x40
    64#{
    eJytlz2S2zAMhZFMGg4niyvspMo50qTKHXKK1D4CW46LvQJbjZotciBUmUkT5j1Q
    ki1RXjmbpS3/iJ+fQAAE5C/ffn0UHz9wfMbxFcd3HO/kg58/Yf7nQzvW4+RPOZ3a
    Gx/48Pz8jHeeqf6UWtsbHzyFl09bqW68f3ysR8Me9ZCp8v+MiIm+AeMeOGJMjxl3
    7BFj/6RzIwx6bQ9f4ziOpT1mEb1eF18NsxL54DefUNM1U6OEzJHcVGaFXvtQXSin
    yQo3UDeM/8oWSxemTba3QCEw5udeZMzmC0/20IVrBsMNwGLUzcAH0Y7hnOhk2Q2G
    +qqz8QphO2L89VUMFqK0h+bXjuHSjQFSmw7G6xJ3MjrrtA2sLYx67R/8RA98KEF4
    MXWT5+TcxCLkRGvasFvMIHWC7CYzZgmYTm647jEy5IzrhZDDFKyegdA5ea6mKBZL
    3ssfG84DEzpJERmDp/X2WhWnkfVACmR+Y0jHQCgnSMTYZHIW3TJiMGjEPDZYeeLA
    MrdMDTCoRJhzFt9nPZM0wiBs5xKK/HZ7eh2sHj/lXg6SJOzr2DBAhxdjwXhKsXQ6
    MHpM1IH/sA9DGsc1Q+9z8TAnOq6g1zpwF7IHvg1YWchk5p1/YZhusIc6Zeo68PlG
    BzuU9lyK3I6OUifjPGMZku9k6+3BvLZCFXKxttdXOq0DTuM8JBbUjU5mtXCtgrqa
    B+pYr6MhWUAKlYCQJuvtGREcMEI3Qwf24FfreEWc57VCgpuD20Phrc5T8kuWiLiG
    oVQEeRN3zwVJgXXev2ing9PMKhNkItoG9pHu63BrJc/WMMK6XkeZ5cj5GAuS4szF
    bxhtOpxn+nDxtWOM9tgohtTIyOhMV2wYxNJlkMy+a0YIdUxFSkEmTlvrDKGemVqT
    FxeWmVx2GM9YY5GiK85D3mVAVNY69AgLN5naaiareb7B4CuLLxuBhWv/sExfqrbO
    l5Trtf95aDriIOLUmoteMd42Vp3EewvFF2ZqeHM3WnR6ZulqbLSvYrytHjC+ZZdu
    7a1R9pml6/MmoGf2fGiyZub88TJhjZGXmKV0eEJd1u53IWvG76Wqbpmmb7nlqt+T
    1Uu8qiwxrbHdtU33dhfGdMkfm+7+xla/L8xVkduMhTm+HVV31yFzh44eCU3rOmDc
    h2/AHBhzL3PPf6J7/lv9Bas8HtD2DQAA
    } "king of clubs" 13 "black" 340x40
    64#{
    eJztl7EOAiEMhtG4qq9wcfI5XJx8B5/Cmddg4lVu8IGcTDphCy7ShP7x0MR45Xpc
    uC9taQmBw+m+dlkurHvWI+uZdeFWedzz/+um6Kv4/DjvSyeNP8Zx5F5GUn5cSqWT
    JkP82tWmlCyHIVlyG7Ymk1wHJvZhKPRhAsUeDBPBZEIwGUZCtJjsrskQG2Fnk5mn
    9GAIYDgiu6ZmDhOQw24MEg8yLyg/aWb+hYk2k7c3g5EVbDBlW2ozZVuazIir2lnN
    ZKQy9I6dXjFD+UHyDNULqbuWmfk9hgBGLSrNkFqcmtGL/HMMEg8yLyg/lmAMR0zt
    iDBGTontyTOjNy7FYLWQM6DBULRrgTBQvXrV9Gt1R+5EyN3qAeRtVIj2DQAA
    } "ace of diamonds" 1 "red" 420x40
    64#{
    eJztlzFuwzAMRZUia9orGJlyjiyZeoecorOvoUlX8ZADZQrASaHoFpHIH1BAWqCD
    aNMylGfqm1JA+fh52wWxL/YD+4n9zL4JW+mf+ffL++qtzXKGeV6bcvDNsizclp4s
    Z8h5bcpRuviy16GMvU1T9uw6fbhMDq8zFKPLxBiTy2TqYHw9PFKHZsiQCWQZCd8E
    Moxk5PFARu8uGakY8O4cpg4Ec9jDFKRVbTRLmEa1ZWycZBmlB+ZHM3BttEOVR7w8
    Y6adryeMmnd/reJ515Z+5f81mMEMZjCD+edM8hmKPqNqNQGGVK3W1R0wpKs7YOw2
    wejRtfubIV2IAdPk58/2JD+aq84nerqYsi+rB0uASTUD86znHc2X2VlqRBhvAUl+
    bHjLkMvEHsZmxDAgI0CPY11MzzdRz7fVHVe+QnD2DQAA
    } "2 of diamonds" 2 "red" 500x40
    64#{
    eJztlzFuwzAMRdWia9srGJ1yjiyZeoeeorOvoUlX8dADZQrASaHkRSS/QaKJgaII
    bUWJ8kxRFEnbx8/La+ryze3A7cTti9tTeunjM///87Y2KXM/0zyvXTv4y7Is3LeR
    2s9U69q1ow3xx4dWZeR5mqon5+ndZWq6nck5l3swVMhlavbtYUUuQzkjhsRUmOGF
    SHMs07U7TPfIwIC1s5pREfShYuC6GiKstvZ0NcJqYLPRUyyj7OHfPgNjQ07VLvH8
    jBm5XxuM2Hdsj5k6kDvlLvn1YP48QwFGZ6BbNzBjU9nNr98yu9WNHW2O+Cfk58h+
    hfbdyoP5n0zxGTKBZxkVwCjGSCUCilXFwJhXDM4dcyMGOUj6RozydNS9c20h9VCE
    7BEXbjH9YXKYrFimCmartowMri0ktcPaArTbuQIxJs3BTPYUaR/ewLgSYiLvRJF3
    qyuqXC8m9g0AAA==
    } "3 of diamonds" 3 "red" 580x40
    64#{
    eJztlzFOxTAMQPMR64crVEycg+VP3IFTMPcannyVDhyICamTv5OGNnb9ayPEgBS3
    /qnT913XjuT05fXrnIq8sz6zXljfWE/pvsyPfP/jYVEpYznTOC5DPvhimiYe8wyV
    MxEtQz7yFP88aVc7uRsG8uRzeHQZSr9nZgwwEGEwwEAgHnbkMhxRy2B7F+ujQDDl
    HaSxYwAaR9VQ+cn/IW0oJrtFbQD8lCnhgWVsTJn9diQM3w+68ZQ8eQyIetn5ieTZ
    r9eOMequ47Fl9tehys9t6UxnOtOZzvxvBn1G9KQbzNrbhD9smbVHWj1OM1avVIzZ
    c1U8du+W73XINNuqP9yTVGau26GjeCIM+fmpjJfn6mhDjHqt26pNUDLkLKBcL/SZ
    GXyGIoy3oJv8HDOOhJjIN1Hk2+oKvccYTPYNAAA=
    } "4 of diamonds" 4 "red" 20x60
    64#{
    eJztlztSxDAMQA1DC1whQ8U5tqHiDpyCOtdQ5auk2ANtxYwr4Q/xWrJiadhQLLNK
    HK+cN7IseW3n8P716LJ8xvIay1ssH7HcuYfcPsf3x6dSqMz5dvNcqnTFH8uyxDq1
    YL4dYqnSlZri44Wb6uR+mlCT0/SsMuguZwDA78Eghl2Y8Bt/fPu2KJAeDROAdCIy
    ySxXgPoc3TsbWhXGADSGVoX5LDIhmatMsl47I8qZya2rIaLodjxq/uQ4aQwdlxif
    XCtx5oyUL85IeUfLXKXj2hC/y//rxvwLJhiYdlZvMAFAZQA6Q5wJ7WJwAUMWDJkp
    CwYMGYud3Xy2xMcUZ0u+THnv5cZcH+N1JnSTqmfq5CT2fMvUPVLa4zgj7ZWMEfdc
    5o+8d9NxDZkyhL8+k5h9rkevEeNRjc/KjOKMPyfFUb4qY8n7pmTGq4xCmBmbPyqj
    9WRkLN9Elm+rb8X3AoD2DQAA
    } "5 of diamonds" 5 "red" 100x60
    64#{
    eJztl7FSwzAMQF2uK/ALOSYmPoKFiX/gK5jzG578Kxn6QUzceRKyVV8rxZbEteW4
    XpW4jpwXy5YlN3l9/74PVT6xPGN5w/KBZRO2tX3G+7sHKlzmeoZ5pqoceLEsC9al
    BeoZAKgqR2nCnyfZ1Urupgks+ZoeTQbC6UyOycFEhy3syGSyyaAtk4mCybwHYoSt
    Mk0QmmDqNEFogqmmQWicwQePOmoaH3Of4X4ujQdjTGtMZq2kRcEM+0lgjQcvbIav
    Rdc/hbX8LJjuekmmt+7giB8+r5Gks+TXjfm/TDd+BNOLQ8F041kw3bzgzCC/fs1c
    et+4xJg9/nH52bNernUfyo25TibZTI420yJXibHcMkCJ1cZoMd8YNXcO/8DjHMxx
    1brO07Tv/A/2ltzehpTx0BMGU97LLP8Qo+4te0bdWxqj7S18IB0hRkXIliGF0S0d
    +VlnwMM4xmMznm+iF8e31Q9Nm/Jv9g0AAA==
    } "6 of diamonds" 6 "red" 180x60
    64#{
    eJztlzF2wyAMQGlf16ZX8MuUc3TplDvkFJ19DSau4qEHytT3mKiAEBAIpLhOXofK
    JgT5W8hCyM/vx+9XFeQT2gHaB7QTtCf1EvQzXP/axYZlDqea59j5A/4sywK917hw
    Kudi5w+vgp99baqR52lynJynN5Zx6veM1VpvwhjHMkDwPksYmKxkLL5IMtqgSQzB
    wGOWZtJIlwxEojB0HZWMj1Y2lEe3Ml6ZJ0OjxFikjSNdMV07hvfHCnz21pj4VPlD
    xrnOMWq9aoZcd+wPLei5emI22V//zN9lyPypGLJuYKZTN5ygbjhBTbiVuXfduIfP
    kviI4ixZL9G6kyjN4ClJBrtOMzgEJINDuZ7BS0syOCnW29nMZ0l8RHHurBdWkutu
    G+Mtk5wc5Ji9vu3RnChXEzPK+cQM905+A/f3oNWNtt2n5mL8EbVF4o+YYWqLTrdm
    pKktNt4xrC0XZlhbLDLQSogPk2RCRnOThdxot8IahhURI/kmknxb/QDHNOZR9g0A
    AA==
    } "7 of diamonds" 7 "red" 260x60
    64#{
    eJztlz12wyAMgGlf17RX8OvUc2Tp1Dv0FJ19DSZfxUMP1CnvMakCosYSMlJ+nKly
    CAZ/FkII2d5/HHahyBeWNyzvWD6xPISn0j/i9e/nWriM5RfGsVb5wJN5nrHOPVB+
    AaBW+chd+PcqVTXyOAxgyc/wYjIQrmdSjCYTY5xMBtLdGGlz4lerHmEztpajTMpY
    RS2IlmCKWhAtzuCNC0XU4vboDJ9X7jwNxlrElBv/FPGWqWcCyx48sRnuZ9U/mbX8
    LBh1vSSjrTs4YpXPa0Ud88+KWVkahk9PZ7ibVIa7+3KGL7/K8MC5XM/NbPb4x+Xn
    /nolrRKMFoeCUeNZMOq+4MzK/jqbcecN6tXzhgiK83LLFvPy+NC1Fp41dcWGqk5l
    /nPC1czWOYF3JolkJjXKW4aMVJ9NlUk02U4cEtOLZ2K6++L0BF7fXyk2ve0enI7K
    N30nIXuiaU+9w2Dye5nln8p088aR6eYNUttZdy06uSzGugvT7qiG8dhsiovxfBN5
    vq1+ATh8w+n2DQAA
    } "8 of diamonds" 8 "red" 340x60
    64#{
    eJzdlztSxDAMQANDC1whQ8U5aKi4A6egzjVc+SopOBAVM6qEP3ESfWx5w+4ygxLH
    K+dFliXFO3l5+74fknyE9hzaa2jvod0Md2l8Cvc/H3KjMqVzmKbcxSP8mOc59HEE
    0zkg5i4ecShcnrgpIbfjiJZ8jY8mg8PvGXDOZJxz3mQQ/owBcjdr3Oeg7S34hdmv
    PYUCmZYuG5PMItPSZWXCgztDRaNx1hkkTBzcJiNaYdKDqyGqmXa87Q90+eyt+ETW
    ijNj1HxxRss7dtQqXVdN/FneryOMui7GaPFhjBpnxqj5okwl7yczl67nS/i8RgSI
    RpkS2bVIUTLsppovNomed8X14wwNpcrQJBy3Y/kDhGnVfJmqVfO0Yww10K556pbO
    SO1/1LzUTttbevaorr2uKudkvM0Ar1+FKdFqrAuc3AWQxacwrTgXppmv7R+4nndw
    YlTWhl+MX6GeoctnZ/ucY2rUMyyTbQivZ5A7DvJ6dnLXRl7PLs9Vl2szYtOWDA2M
    zpjSxfR8E/V8W/0A+lG2V/YNAAA=
    } "9 of diamonds" 9 "red" 420x60
    64#{
    eJzVlztSxDAMQANDyecKma04Bw0Vd+AU1LmGKl8lBQfaihlVRnbirPWJrR1YdnDW
    m8h+UWRJdpyXt6+HIZcPqs9UX6m+U70Z7nL7RP2fj0vlZcq/YZqWUzroYp5nOqeW
    mH9DjMspHamJ/g5SlSq34xh75Tg+dZk4/JwBiNBhECB0mYDXY5B3mwyEmsmSZGic
    tZosSQagVrRIgqEbK0WrJHxoMiIWqfH0MCYVJt+4KeJSV0+IPXvoos8kbdo/WEuJ
    Nfy8kuVRYMRLoUbc+SOFPZbpYlw7DPPPaXTAE0kxPAg209ODzB40mc2xsEmSwRKg
    whoM76yt0nHnj7QZbjpjLp3Ppj1aOosx57tkUOSW5Wdr/VGMsY4pZrf8c8YzdocP
    PbFwxPS38ucqOd+Yg5657FkTXGuLZ41yrXWGC0yGO+9MPXUTajWJQegzRbf5bloY
    LDda7zjBoIlyBqx3LrennasIqlXnRliVXz6faT+Vt1Ute2jHtW6rmkyE2J7vhUEQ
    sY2aaa0/G9NYxzZmt/w5ozJYMygni2Ka3X7G803U/7a6H78BawWaX/YNAAA=
    } "10 of diamonds" 10 "red" 500x60
    64#{
    eJzNlzGu1EAMhg2iiUYwV3ii4hw0VNyBU1DvEaYdrVCu4Daa5hVc4lVcwRVShFD4
    bU+yySS7byWeBJPNy77Jny8e27GzHz//fEs2vmL/gP0T9i/YX9Ebmz/h/Pd3vm/H
    yT50OvlBN3x5fHzEUWcm+9A0+UE3ncKf9y1qN14/PEzPDXmIz2om+nvNmPsX0Uz5
    Dnv+keZKGOJaU2nSjX3WTx6jT2dsK02vl/Su6dN0pBmzgZSep3GeznmtyerSqcs2
    HCPRHL1ofuf8bWW8rySuD3F6wtU/9Epczq4RvqXhi6a/aEYzYwpEQtDwrNGVzBo3
    tQ8UsZkmmuYJ8JYT7ZxqSDW2ksaelSbgnnbpDY0UxGvLcf+EKDRrqAxbe9zPFOra
    2dzZrMviJSWIc4o9w8PWPxZ3qefAKXHnZ4+PFO5h8hiI9aYybDRd9GzsctK9hCOO
    GRkieWZYmu446tiIaJFLhFI84FAhuNazR6hD5HYcSiVa1E1CCTlELSdjNlAiwSDF
    YLScFBgRSslvppi04wSEkCX5UEyHWzacgmWxDDYcwzI1HKRfIVlhwp6DVOCqqRgk
    fsspHKtGMYFCl3b2cITmYk0JncSWQzPHMMk81vpHrZbZmjPM6XfxQn65RhcUzsZp
    /Zz18SO1ZqBrnDFoiBST4YNwzFEzNODDubOAHXAmzCZ1b+akobrGOeOcYjT66p8j
    zllBwAzqB/j5kANQUEwSJAFf5cAo0+i3odG4PeQY1XCbz5HgHzZMh7AywolEaDgE
    P4cFoxykU8MRYl6sgcYyruXoghZMksBxZ4/IxRq1B0kQdvbIGpNQ6vEwtf4ht8af
    wPpA7p53w5A/yynQPlfJSkBaakKI6q1d/THMXFvgLI4HdUyLxlyjUNHiUR2z/ua1
    DpXoSj0MJXDOqiOf7vYcLbmdwKGpWP/yjtdwULsVj0Cwt/KLpldOPcfoJfIrsLfy
    RWNtg91Ib5JqsbXyRaPtpyvaZsU52sW8Vc0ab2Pw2cyJqvGWN2u8HbKEeF3jHNaF
    XjS1BW/sodqssXZ9/OZW3qzLfAaNWNRbjrVD15CvHUvc2nPx86TV4oYmVk19DWv8
    A48u/dRNzXpq4+dR/6vRn8Yen1Gv2MQLc1Uj9X2tT97KjzTdmOurn996nRvQXH2H
    XGlujv9Ro45+Cc1z4y7NPb+J7vlt9QeRdPNP9g0AAA==
    } "jack of diamonds" 11 "red" 580x60
    64#{
    eJzNlztuXDkQRWuMSQjCri0IE806nDjyHmYVjrUEpoQw4BYqNZgo8CYceQuMBujA
    oO8tvu5pVttuBQ5MqX/vHV3Wn6237/97Lb4+4PE3Hu/w+AePP+RPv/6I+5/erMe+
    Hv1XHh/XC3/w5vn5Ga+8Mv1X5lwv/OElPP0VpW7Wq4eHeW+NB73LTPkFTK3tHnOq
    td5l2vxtmR+kQa+Z/9WGqeqouWPpDdP8XbaBVbP0bjMyp7oYUZlLh8xVDHVFnVvJ
    mLp0FJevcqHza63/ugyuDE3SnXHxC/MZql/IFKqlYtmZdstgqwKDdJS89qInZ4bG
    wbxhqciQ5RgZbnZmHKktSypzcLPD5s8QDzpZqjNKg+C7exLsORhEEQbdxOeKwTUZ
    VQy52HX8E0yuxbM2U+Femz0rzmQyXJ8JoZbo18oXXc8KewqwMkN8Vt7BKLaYgqdR
    9SiZvX4QZjAiyIMwUpHhOzCIcu98VqkamJFNWYR03IfKQGJ3Bltkgkwo9FjLQouu
    GGSadccKG7IAWJZ2xlikk7Eh4CQd2Bg4MmkT7/mCTN4ZFaNrRF2EZWZ+7YrB36OM
    xWz6XgBRtTnoIKg0IftQlYxIWwp7UdzN7Jy2A0iCUNAZbJthBRBWNy1iKegoAy1W
    2HxMxgnmxL1gTRfa6TrWCvaKfg2moyt1aPIJBS3Br7msaOw9aVlbKRwyuw53y4lG
    ZDDUMYlx9qBYlYtO1qAjlLFiT7Us+nTKEnT8Rho59Y/sV+pY1HGZ1nIi0T9CJ8t3
    dHI6Db8h9amYy9zEEO7AVvedA8Floo4V6Kj7zhhTZsa8W7notNLNrdkY/2u3B/2B
    +HBmcMDuNY8YN4YWQWSc0a03MaQRQ3xy4y2qBLUWmCQmTdwKKBS0R/ZS2HQK9ur+
    rQC1Jmwhz8/GQIglRm9gORjcD/FhAcrqcnaW8VwRiX6hm89NzBHEjrToV9ZLF8tg
    Aw3NFnKxOn2NBEUh07GdSS7j1UgSU4ajZs97KgvwK/CPXvToe/WNJh+AOFpp0553
    zD4OShwSDIEdd0LvVPrMHuUIsrlW6EGeW527dRymZNrGNJ+9GKLCcwlHDxk/bS8M
    P+E+hBINRm+Q8aP8wvD4wejFWB+8MiqZdZSfGf+kHG2IEQd15RxcR96Z8eOQI1zK
    GDwnv8MsHexlPEMO5jiCd3smopeYbTBI53GUB7/QMzAIx7+7HnVWfNh7xcus0PXd
    HjxVZzih6LuP98icFgOD1E83Mnt8PKJuc+YJ6hU2Q5wv8emdedVj4m758q9lfgy6
    YwOJ9zvthsmHjuqleCLzo++Q18xP12/IMIZ3mOOL6s+Zu+tFzEv+J3rJ/1bfAGnk
    dy/2DQAA
    } "queen of diamonds" 12 "red" 20x80
    64#{
    eJytl0tuFDEQhgvExjJQV4hYcQ42rLgDp2A9R/DWGqG+greRF2TBJbLKFWqFNFKQ
    +atsd7e7h3QQ8aRnJu4v9a5y59OXX+/I1jdcH3F9xvUV1yt6Y/sn3P/5vl7jOtkP
    nU71Q1/4cnd3h0/dKfZDpdQPfekW3j5sRe3W65ubcrTkhg+ZQv/PxHiJ0wswJeJ1
    xFymYwbajpnLP8n5Sxp4bY++S7aV8MpNOq/9Iv3ijSBPPmFrUuYyLYxjSRCkRMRi
    sxXMOoZgMotXotoBW6drTHGV0ML6HeP3amZZ/GIYobcFG1LKPTQ+jAxPkxjDzIXW
    zLRm4BvbEiFYrAuMmt4ZVscqU8pjMSRODHkPoz2dQVyaHDN98GtmpNuj7MCILfz2
    SJ3ZyEGcK8Pr+KzscUyzKmIpPc5rv5xGrqvSMLZ8reMzM7ahYM37Os4rOaKVoguh
    vM5YIkS1UYvDwJRmsu6K/cWOkbmGLYjK08DoPY20T5XyZLrH2kC2cF8yVXHJ5tmo
    i3AzM4pdEOWIskdB8sYvTjI3lQshUfQUeFurRL71l3MhMaUYA++YXj/Q44KHOkAD
    UyC9tz55SMqZrM9WjGC7NoY2LIwmhTa6og+ezaRExgByI+PCrU9JNIQ+KQM/cxoZ
    iMmw2rsYVEgi5MbnvV8EZWjngG+QANEj43y4hRbumUj4Fp3fMapCICcGGMQQc05b
    Xc5HNUiNNpOjy2MMXXRUGSsMr1k7346+646ZUhlLbQ6bGOKPAmGQWYDQkO7sek0u
    9RMi7PSabtiMWHqXzqMuj4JAXpPJqabvmQQxlYEgxNLKddTlM2n0NZ2kjCQPMZv6
    QbbVZI/CVmWSsjPRQ5wVgg4mK1gog5gNE3ytG251JgliZBNnhRK6oRcs6pVHRgtc
    yxwp4DaBd36xQtYxrhqUM+2YQgHSKYW4nHDyY2QweLSrSJ3RtscAED/Wjw4eqbHV
    OKGzs86RTX/p+NLCUMiGTNnlvfrSa7IdYm7jl41LO7zaoC47pg62NjD7oN4w0uYt
    TF+G+YYpdWiLbV1hJmNs+NvkvSJHb/bD2s6C+WxZmHs7XOthJMT9GMNhNTN2k+uh
    Jm8XVX7FXOoBXJkWB1Xld3Lmw7ozOq5ptKcd1o8LY2N28KvJEZLOcJmmTXzqQSyW
    rVr0A1NWjNVJO4V2tdrjY2NFp/faL3sM6nGuxBmddpWxfOk4BIHntiHO9ljW8i71
    ic26WfO/MKBad/r65GcnQi+ZysQnniF7vqby9FLmAHmunOlIUPPrgLEYvgBzYMxz
    mef8T/Sc/63+AJ3HlmT2DQAA
    } "king of diamonds" 13 "red" 100x80
    64#{
    eJzt1zEOwiAUBmA0ruoVGifP4eLkHTyFM9dg4iodPJCTCRM+3utiX8NPkNaY+FrE
    4BdoQSg9XZ5bw3GjdKR0pnSltDIbLrf0+30n6T0sn8ZaydJBX/q+pzyVRD5NjJKl
    IxXRx2FclYp110UUj24PTTQNjG9jgmtjXPAtDAkHjXPQEHEeGW4uawJVQo19bIZY
    yDgP750uGl5z6uelTCwYr1QR7h+qBpoQFxuLv5nPeGx46QIm/fOAkSUnb2TJyRqe
    CS5vmIwqqjDS1KixLxvVixP3FdC9D8tt1kzF3/yeCQVGTV5tgpq82ujJq0zQk3dG
    46GR5zwwMNqZqZlZY4aNGTBowGTcYR/KhjNvZOPawOinUZ3RT75KA6LIlLwTlbxb
    vQADV0S89g0AAA==
    } "ace of hearts" 1 "red" 180x80
    64#{
    eJztlztSxDAMQA2zLewVMlScg4aKO3AK6lxDla+SYg9ExYwqIdsDG30YaXaXzkoU
    Z5wX2bKVkfLy9vVQunywPrO+sr6z3pVD71/5+elxqJS1n2VdR9MOvtm2jdvWQ/0s
    RKNpR+viy5M2ZeR+WSiSz+UYMlSuZxAgZACghgxhgonnwyMl5uwwyG8CSEOaYUcQ
    +ghnQ4rBwfwaArK+t+cgGOu7YZw1vITp4vgFe78uZVAOBdXbd2GGV51fMMx+BYdV
    Z0/lTvgMAoUM1d19JlYzMe/GjzFEN/kGJzOZyUxmMv/EYIJRqdFjUKVhj9E51lgt
    R1T5HI1Vy+jM7TAIZnqNqaIoMiVAn3PVmRgMI+RG9cZftY0sMK5gWl0WMVUyZhX7
    vku/0PNr36Vr0R/G7rRlokDsDIYMZBgbVYbJxHwoKSbzT5T5t/oGyDsmgPYNAAA=
    } "2 of hearts" 2 "red" 260x80
    64#{
    eJztl0FOxDAMRQNiC3OFihXnYMOKO3AK1r2GV7lKFxyIFZJXxkmESOxfHDEjRkjj
    NpPKfeM6qe2kj88ft6nKq7YHbU/aXrRdpZuqX/X+211ro6z1TOvaunLoxbZt2heN
    1DOJtK4cRaU/99aUk+tlkUjel0PISDqeIaJ8CoYzh4xQ7I8aChkm8gyrj0X/9SjE
    qJLLWDp3LMONoYihgfFjdwyYQ8ugcVlGgD/SkHw0w+OjKKP3PpjRWdc/OKafwWYV
    vNPemz2GSUJGcnc9FasoNpx3eSJW5SQ5eGHOy+SYYYoZm12AYZvJgPFpGuUpZE5U
    E2bqz18zbhaDOg8Zs15ABsmF+X8MTzAueT3DLnk9Y/PJWU0HNjGOaotlUG0xDKwt
    bX3uzKDaUtf53syZawvbzQNgxk3IDlM3kz8zYhhcW76ZvdrC424G1paZ+JEAafGc
    Q4YiQyBWf8uEMsXMfBPNfFt9Aow0CNT2DQAA
    } "3 of hearts" 3 "red" 340x80
    64#{
    eJztlzGSwyAMRUlm22yu4Em150iTKnfYU6T2NVRxFRd7oK0yo0orYACB8aCdOB2y
    CfnKQ8aYjOTr/Xky3h7cvrjduH1zO5gP75/595/P0Eqb/WnmOXTu4C/LsnDvPORP
    QxQ6dzgXf1zqUCs7ThP17Hc6dxkyrzNoFQxoGKtgQDEfDtRleEaJcVd2gQvBlwLB
    sGCPLUXJYHBDJYr18SMkA2oGYB/Gm7wvkPeVxf8YlNGTgPJZyJFR8HrLdSbMYZKo
    noUbm8JEsWIwh4lixZAIE4Wf1g77kBT7mZB2+Q8OZjCDGcxg3sSggpE5aYNBgC6T
    cmQRzkoGY65t5LgV08iVNdPKuZmxIZU3cneesw2pvFUDRCaP3KHe6NU2GKuzFxkC
    RR0Fm7Vfuc7QqiErplmLZoYU+8f2Gc0+JA2DCkYx566pGM07kebd6g9PV+vB9g0A
    AA==
    } "4 of hearts" 4 "red" 420x80
    64#{
    eJztlztuwzAMQN2iYz9XMDr1HF069Q49RWZfg5OukqEH6lSAE0tJsUWKsqm0BoIC
    oS0rpJ8ZWpZM+vX9+2FIcuD2wu2N2we3m+Eu2Sc+//mYm5Yp7cM05S5u/ON4PHIf
    LZT2gSh3cYsmPjzXrozcjiN58jU+uQwNf2cAIOzBEOEuDJ4dD7ICUCvJsDB8RXKs
    FcVgNkOlgIw5mkAy0M/ImFcYjAeHofq+QN5XUc5jUHpfFAjqWcgrZ4XHm9HCYHGz
    KMmffKbCzawYBoubWTEMCTez0jNX1TivSQzLY5B2WYNX5qIMdjByxq4wCOAyAMZR
    zSCAcVQzahG3GVQvjN8zAI0/uywTPAYzQ1tMzBLojGFTrsz/Y+wKt4xd4YZprHDD
    LHNTuQuSWVZ4I8cZppEra6aVcwsTcipv5O4Sc8ipvFUDzEy5cod64/TiWatt8FQM
    bTHJq8fEuqyXQcmgYlLdulJDaqZdi6qYtyQzm0iO2ZFepi8el3Gli+n5JvK/re7H
    H4xdySf2DQAA
    } "5 of hearts" 5 "red" 500x80
    64#{
    eJztlz1OxTAMgAtiBa5QMXEOFibuwCmYew1PuUoHDsSE5Ck4DmntJI2NeOJHem7z
    8px+dZrEdtqHp/frieWFyj2VRyrPVC6mK25f6PrrTS5aFj6nZclVOujPuq5Up5bI
    5xRjrtKRmujnrjbVyOU8R0ve5luTidP3GYTgYMDRFxkyGTQZ6stkQDFpBOkBtQKq
    L7qDDWtFMZiboVIaBiQDfkY+8wGj5vmAYZHjAjmuXfkag9L6pkBQayHvLArNN6E7
    g7uZTWF7ck2FmaI0DO5mitIwUZgpCjj8x+XPjriIGE8Sg2fml5ggL4Qu0/HDhun4
    c8304qJmevFVMf041cyJcoIn//w0g5LBljnI8/Uc9vYLzQzkzPw/Bh2MjOEDBgFM
    ZoskZS5IBot3D3LLxgxyS2FGuSXvz0Zu4X3+D+UW/HwZGjFs1WLSe5mXGeQWZozc
    kplxbgEwnCwzQyT3ZQivqc14/Dl6GMfz2Iznm8jzbfUBqcKs2fYNAAA=
    } "6 of hearts" 6 "red" 580x80
    64#{
    eJztlztSxDAMhgNDC1whQ8U5aKi4A6egzjX+yldJwYGomFFlZHviyLYSmd2dLAVK
    vI7sb+WXLCcvb9/3Q5QPTs+cXjm9c7oZ7mL5xPWfDymVMsV7mKaUhYsf5nnmPJT4
    eA/epyxcoYh/nmpTjdyOo7fka3w0GT+czwBwF2E8mQwTZn96GG5MMMSdAxqlYHgE
    FAZRKZKhVIxaCR2SDCSD4xlfjQtyXKvyO4ak9azAFesu/7koPN+MrgytZrIS7ck1
    FWYWpWFoNbMoDeOFmUXp8VUK3TJ9zNkM+YvswX/mSoyTFU5lFD9sGMWfa0bbFzWj
    7a+K0fdpyVwoJvTEn6MZkgy1zFacr+ZQPS8KZkdahjoY6SEbDAEmAzSGaoakU2ww
    xYLrDBWLcDoDKI0dzVDBOJURRZQYXzPFCvEzKXOYbRdVTjJ5hXZiS2Z2YsvCELZj
    SzqfjdgSz/m/FFtwBYawHVviSyn2Y0tkjNhCedZ1ifPTRIGTGFiNSV89kzGli+n5
    Jur5tvoBfRyUSfYNAAA=
    } "7 of hearts" 7 "red" 20x100
    64#{
    eJzNlztSxDAMhgNDC1whs9Weg4aKO3AK6lxDla+SggNtxYwqI9s4keKHtEMIq8Tx
    SvlW8eOPk7y8fT0O0T6onKm8Unmncjc8xPhE5z+fUpE2xX2YplSFjX7M80x1iPi4
    D96nKmwhRIfTNlVh9+PoNbuMzyrjh98zCKAyAOBUxuNhjGwzUuNCQDqyzeRhCEhH
    XAtTGDZOwQBnwM7w9jQY0a8GE433C3i/Vuc6Bnn2xQEn5p3/Mzs03oSuDK5pFifm
    43PK0mSnYHBNk52C8SxNdixatWjeh2ZpDPpd7sHrmbrmJVPXvGAami8YTYdHMn+s
    eclUNb9laprfMjXNF0xF8wVT03zBtOy/GMdPuCpj6bthDC1zYZhTizZ20uGt3V8h
    jNBfEyxri2mN6tieDBoYrq0GgwAqs8ywSOc4g3nUO5pfmI7mM9PTfHo+K5qPz/kb
    0jz+vAz1mJhVY8J7mZXpaD4yiuYhvw51NG/ST7zWQQwooje2WTUTY/kmsnxbfQMa
    WW519g0AAA==
    } "8 of hearts" 8 "red" 100x100
    64#{
    eJzNlztSwzAQQA1DC1zBQ8U5aKi4A6eg9hFot9JVXHAgKma2EivJknb1W2XIJCiW
    nVVeNvo82/HL28/94ssH1Weqr1Tfqd4sd759o8+/HkKVZfPbsm3h4F70Zt93OroW
    67fF2nBwL9dEu6cyVVVu19Vq5Xt9VBm7/J1BAJUBAKMyFq/EIHXODUIGss8UoWuQ
    AfCxY2iGIvAxZ4AzIfCJFQbL32owdobh/bGh1TSC0xjk2VMARqw7/2YMaL4JzQzm
    NCnw+fiasjQxqBjMaWJQMZalicGMqzPO+0XVGLRnOQdPZ9rOS6btvGA6zleM5uEl
    mawwWt354zQfOY/HDhpzGDVP+2xu6TymbNnc0vnUK3YCFM4jpG7ZFuM0Tww/AQp/
    QI76uowJMxiYdp+PsZvR2I+588J35lBMf2ctRF/bayp06LghBq152GdmnL80g5zB
    mpm5tkxdowblnAxOMIW+LQaZTj0mrbBIZziDcdYb97iKadwrS2bkfLg/K877+/w/
    ch5zdwYMTPhMiVQGjeY8xov3wHmIf4cGzvv/raNyaaa8ZjcY/NSdV8sUM/NMNPNs
    9QvETVBI9g0AAA==
    } "9 of hearts" 9 "red" 180x100
    64#{
    eJzFlztSxDAMQANDC1xhh4pz0FBxB05BnWuo0lW24EBUzKgS/uSj3yYClsVZryPl
    xbEs2UqeXj5vh1beSn0s9bnU11KvhpumH8v197tedRnbbxjH3tSjnByPx9JWDbff
    wNybelRV+XuwXblyfTjwXvk43O8yPPyeAWDYYQgAdxmk/2IIsFqhBcMAQLVCC5qh
    rgYteAYkA2lGzWHMaF+cYFqRdoG0axW+x5DsfRHKkFH4ot/ZjMFJqKcg/UWLsb2F
    uT/p095NY5D7aBxDMA+2ego4YhgFwzgPEm38rMwySLcuLMP11DCknFAVvM+wZ9g8
    KmTId+NjfpodsQB8zNMEkvc7wRzznVsXAAdxSCfjMBOr52L+OOY1w/JOLZiYDwUT
    86FgYj4UTMyHwhlywZ8xKC9gyGRsT8xhxhcJn2Zi40xxeMm1s6q313KfuNN7QmZv
    Se1Rcoa16id7JimDBEMJRm79EDysMCTuO8UsOvS5aWIWa4Mc55ggV1qmOYh0zl0Z
    7KkcfO5ex4w9lYuJUcKFY768T02ht8XMxm4ynGdIMuSZ+B3SMOG7qGE2ysUZt+o8
    Q3bROWbzcp7JfBNlvq2+AMWyKIX2DQAA
    } "10 of hearts" 10 "red" 260x100
    64#{
    eJzNlz2OHDcQhcuCE6Ih8woLRz6HEkW6g0/heI/AlBAWvAJTg8kGvsRGe4WKBBDW
    ovwe2d3TTc5qB7AAiTM9P93fvC4W64fz4dOX99LGXzj+wPERx584fpFf2/l7XP/n
    t36cx317yv19f+MDHx4fH/HOM9aeYtbf+OApvPw+Sk3j3d2dvTX0zr/JmPx/psb0
    XRiLN9jzg5hXlsEfmU3N1VhrSjFF309HPHYmPthLTJo6UwN/rmemxvj8FKM6q2bJ
    IKPZHJh4YfA58ZDYh7elMXT0gYmN2a3tjB1s3hhFvGQM0+JFXmHUr0y2lHx9eLnY
    89SZZwW0YHBOYGKEQRvz0pAHw72sM9m8993GgfE70+w5Mfa0mbwyfV48+7wzlYyp
    wI4DU48+NHo0YeI405nNz+nA4Cdmu804L775+bDu+Ekig+T9F0jJjALPtdsZCb7a
    qtOcWLyNa+FawIhvNYDIMjMaKFCwCCZRcpErOhqEU1mKIDrCsuz2nHUUOiU7IrnV
    oZHhu9cl8/JSSnFVJsbDWnHKyxxSqTTqIMLFRRUSmQjWYNRRcwEQfy+uIRYnHbHa
    II6OjLGqKqlBlNmQPDBeXYPgg9CRpa3H8V6quUHOu44U+ODE0MmlQRKCazf6nGVg
    oEMnmwUHw6CSXRlsbjqAIoSQ8oFuhNCkw0WCRsCTnyg06XCllRFee5xAaNZRvDrM
    PQo/FXHzvDJOI3UQiwrfQFY+X9EpWHuNQRkk+KruBuaKzeO9bGT8Mtg86xjz+zT3
    SYexMfpw1tGwnNdi1rHlb2kL39a0IqxnHaQmgvAQG7MOEnMZYgw6/szkHmHHWIWR
    Z6aIG2I+iNpgz4q01ImE4MfzvdyG9BQkhKwcGHoX+b6lcmx7mIFBtp9LAjLOj4yp
    q0Rwr4IyRnMnHZZUXGaPC6xiqFjBZqaUpbXBUIROlisMixhLHRO9xck1ZmFdRiXX
    nqAox1d0MFiWW/OphrJ+YdJmT+7NhCWVncFf7sW24VhYpTecptM7zM6w/Tj2xt64
    4D3bOtXGtDbm2BvX5iZfZe14O9Pa4ZHhzXqT3pl1e4D82plTB94ZLJ/uzFebGbR5
    n9A2V5tVbdsR7DZzuwBG87rTkNUcyp9jlfZ0xl5h+rz2jc3ExLgxrl8K+47p4MPN
    zwh8hD5ydd15XfycKufEKhXbdq2msO7gzkz74rDnIxNrv3ZYU6POa3vIA/PN8TMy
    LXS/A/PWuIm55T/RLf+t/gNy5vcz9g0AAA==
    } "jack of hearts" 11 "red" 340x100
    64#{
    eJzNlzGO5DYQRcuGE6Js8wqDjTbwKZxstHfwKRzPEZgSwkBXYCowmAl8iY3mChUt
    0DAG9P9FqbtF9Wxv4GDVrelu6emzWFWs4vz5+etv4sffOD/i/ITzL5w/yS9+/RH3
    //m9n/vj0d/y+Ng/+MKXl5cXfPJK87e01j/44iX8+TBKHY6fHx7avcMe4l2myf/A
    5DzfY04557vM3H5Y5p0wxGtmU7NaF4lmtl7eM/mpvcFfWsDgkBsMnPX6Bf5S6jSx
    fj1e+zDi+8zTqGMtalzlL7Egk52pWWCMqaw6rd1gkl8KZbPnyOiSYIxJKPE83Y35
    0plXzfAKXmFy5vT0drH5zZGnZpDh82GSLn/K88jA485oLpsJZ6b5YHMzzvw9ht7K
    lImYuWwMn3y9xGuVkT4ZXWj03s+eui5jpXHyndn5GSd/QcZqbABDZ+joK+ZEBPFU
    WKJSOtPmts8flylSRYo8axz93DyS+BVVpFbRWA6MVQ7BkU6FWfo8MfKDTqBD/K5i
    rBpLpe2DDmyEh010QVJHyBQZmKgF0TaDXVrTqUIG5J7BU7VkSFm3GZNTkT0DC7WG
    5FnKm1Cj0J5hBAQQFAsDq3TSYDNuGEZLDUyEo+SoA1uT5SkkGkTLoVLK0c8yZZjN
    ZSNAjzpYdkh0Mlh+lgt0VEd7MH0LCwbDpTCVW/YgPXEvOyOhwiZObM8EBAfPg0H+
    hEJm1JGc+soLcLdMZcLE6qATcsbig4gnWEYSStCDTuaMcqrQKVNRDJwGPyugKIrC
    onlStzlk3esgnqg7qouCgVmVk0y6H0vce1JTqUVLnjAWTBx0apCoaYERyDaBqJu4
    08HDGCuhitUSUIToiaNOpWsXzj3rhIGxDOKgI+o2MwWzcpIMz0GHcfR7YBakCuO8
    01GuFVtSc+8tmcXebuiYJc8wMBPCZ8MaZKJDJ3qmcrCE1I037ZGe8QgbElFKG/2M
    UgGIDDyzFquDTuVehCuQaYB4mg7+4U1IVfcT8gmjIcWGdUqD6mKIuHkfZH0ZGF8r
    M3ROlXseSOlUxhqF288+WixeOm3JY41CvdDZq9zyL6ti8+I3MA1FrtsMK3zHNfoQ
    f1FJxRPMq8yuJPcyTSZhNWAIlnF2/x3Dck8mu/N1lUFnuGLYNpxBUFnn2Vh6hzkz
    3n6c6cspsj/1TnVmvI3hh6EbGWv7KpPzhfF2uDL0S1tl8OS7DBvL1oEvzKkzmlcm
    HRhv8/yxrjy2/21HMPq5YQH6NgLz23YWV0zXscVJVLF2ZE4r41lq4Raz+plRxbKz
    UJyZr5nNz77VQDwDyuC28zoz82pPM9+shVLPO7gj07wPQsf3UNe50bd37+whr5lv
    Hj8g4xn3bcZjcY+5e3wX8z3/E/1x93+rXx/+A4IDtCr2DQAA
    } "queen of hearts" 12 "red" 420x100
    64#{
    eJytlz9u3DoQxicPaQjihVcwUuUcaV717pBTpPYR2BILQ1dgK7BxkUuk8hWmCqDC
    YL5vKGlF2hs5SKTVaq397cxw/tKf///xr9jxFdcnXP/h+oLrnby35/f4/tuHdvXH
    vb3k/r7deOLD4+Mj7nxS7SW1thtPPsLbx1HUi+Ofu7t6duhdOGWq/DmT0pKmv8DU
    hPOMWaZzBtrOmeW35NwIQzjaw3ctpfh2bsLDcV327iWvZ00P9TlNNSxTx2hQn4sd
    YUnp6TssCUcf4t3n6ktJOCIMTROvgREwLiUzlUwypi1oZaApt5VA6w3Gg6kBR88s
    D89Xm71IESCQhNf3xjz19mhdGeX5bMgD15WOjJZguvDpFsPl7Ewz6Kma0qedSQmy
    dmYhg8e87zZz3YrDVgqSOixeuG1xjzggRdsvAgWZa5E+OwMXp+YerI4FTDEM0XTw
    M8IUmjJbQF32dNgYNznYUw3CS6LUF4wkmipVaTg0r5HrGZHYbCGiSarjNwOTY4Qh
    BcEI6sA7pMLAZIkzZJQc8BFigsPyQscg7M5H3Ev2XuByjaqXPOjyzuWqyFbJVKUR
    67j0jJ8l4hEYqIpUBSelnil4AhNYYzBHNQWs3/X2zFEcHQ1BDj93UVOU3h7PisBy
    NEJZDMBclLm3xzsIIiSFy4c2kUtJgy4rrbQ1Ayy0xDIPNpsgaSVjvoDDysDMaRci
    DoJ9mUfGN0GVeW3KX5FDQTNzei2VV+Q4CmLSILu4JjGbh3hFLBouCRaxahQ+9HIi
    BME1Ck95YdtDF8lu0JX5Q0beeeYR4zbG1BdrPy07sITEB3HwT74YRIMjqxYrH+Ne
    XGktV0JCCoqbEwSNjPjWlh1SjMrgnheMyVnW/i4s7sE/JsfqzqJq4Z19zzizOegW
    1NgE9cylMAhIV0syJQRBQ66iHJiiYslaCbmxdlgxymRPdKKyScioi4WCYsDvWWKo
    L0YwjzFFq0B9JSYAfu9jdKN/2MWi9aHcsmN2zRfHeEW2AchDklKOXqC+Xxf7CFU5
    Fqcwj1we6xTzC+ZUq0INGGXQHIeYss85ilE2u8CnzOyRYU9EE2RrhcloYWwSI4NM
    Z+dYGzDlphfMwnKwCW39l7P3ykyNYZ1rMFswFDgcrvZwbITapkjrpBxSbcpsDBtF
    S66JDJ6oGS7X3mtjLKxTzaytNqTcwR4bh6HaKJ52Zh2eK9MG+Tplrwx8rbcYjm7O
    cCTPkcGY36d+2wMYg7DvNnO7ELbdgw1UswfbmH6m9IzVT65SbzBWXhz2zNTenm1X
    pCQvyEQthznYmG135W2zlYvtwnfGtmVh3aXptm1rqXxllmn9w1puO1FjHZN+sYfc
    YjrVXx9kTpC3ypnOBK3rOmHMh3+BOTHmrcxb/id6y/9WPwHf55TR9g0AAA==
    } "king of hearts" 13 "red" 500x100
    64#{
    eJy9lztSxDAMhgVDC7pChopz0FBxB05BnaOl4ECqaI0eTtavWGYwcWLHE3/zS7Zs
    bfb1/fsRtHxyfeH6xvWD6x086PuVx7+erOZl1RvW1R5ycWfbNn7Km6A3hGAPueQV
    N8+lVFXulyV4hRZ0mQATGJzDEMxhgHAGwwS4jMWvy0i40GPMXI8hEYG/M7vFixga
    YMCPF/nrHAbWWeLlrc8shveYF9MwbX0O7j8YnS04OrbN+wxVMr9mzEpskoG0G0+c
    1hS69WxGIOlk71bMbogg3GyWDJImceLLuhXDA6JvZqTFw1jC6DAeDdAZo6aow+jE
    udj024wqHM0ZY57I45xJ7xN/RIjdwbhQjTU0GyS39lvrLO9pv9tMdEh1bAIVQzEC
    5k/iTsYgJPPC1t5QY+JH9L21x0xoX+5EptjPMkY6K4Lmfo5CiIVMfnaiUCGTM4dH
    mUxxBlWglCmYKJTLlGfZtjsV6jkTI56f+CqPZNFsM+Zz6DKh8qaRW0/yT7lGlTRa
    vk+FQsVQmUirbNxgqjLG5PnxhCFHZui3R8apj1z8+6Wpvf/FNcToV6Jja1osIJAf
    L/TjNcJM+eYYjftle2PkP9HIf6sfYx2h1vYNAAA=
    } "ace of spades" 1 "black" 580x100
    64#{
    eJztlzF2wyAMQNW+rg1X8MuUc3Tp1DvkFJ19NA85kKauqhBNAkggUmcEG5MnPgoS
    epb88fXzDtK+uZ+4f3I/c3+BN5GvPH85pF62VW5Y1zTEi39s28ZjlJDcQJSGeEUR
    P461KtVel4W8hktwGYL9DIoNfYa3HFyGcIDx9yPT7p5NBpUizZQStGwXj9wXkGW7
    eCRjDNsRIFdk+lCiIXQZTCHTs2KI+Qu98BCDA4zhZ55HzBgrxhACUrbnRozlkuTR
    /nk1mHqByxBb4MZzI8YeZ67cZCYzmclMZpx50jta3vd9BnWS9RjUC7QeM+tBkT/L
    XHllsMyfRc69aS7rC7sGyCqnf9UJBrOnbqmLovqvFGOdTGQoP1OtJpV2OWOeV6o3
    m1aQFWNYI8nP/fAZKn5l2o3VyvYGoz2iGMMjxn6cNsSMfBONfFv9Aj95nK32DQAA
    } "2 of spades" 2 "black" 20x120
    64#{
    eJztl01SwzAMhQXTLfgKGVY9BxtW3IFTsM7RsuBAWnVrFBkYWX6OXJqZdlEnrjvK
    F/lPfnZe309PpOlT8lHym+QPyQ90UPssz7+eS67TrDfNcynWS/4syyLlasl6U86l
    WK/VJD8v3lWTHqcpR4mnFDKZLmekOWkPhhOHTKa4PeIoZFjHvWG4qgozlYUay5/3
    gNERMQzoOxNZR3AMNRqSfaWpi0vIbPRijPkJvXQWwwMMtfMlz5kNg/uVOJs2d2LM
    WsqIbs9Xh/EvhEyWHgzEcxyrQ8wvd2euwLBfOaFuIIuPZ8T4ddFhzl+n/2T20hav
    Y5Dh1k2kq5jZZ76y2y86TJvuzG0xO2m06v02gyI4YGCsej8wVqnaP7G2cL1/Ym2x
    W3VPE8xKuAVtYXdwQtriD05IW/RcZh1BbbFMT1uq2IDz5cYeagvw3tYVIGW+BuI5
    cuTH8AImTEPMyDfRyLfVN3+MWqT2DQAA
    } "3 of spades" 3 "black" 100x120
    64#{
    eJztlztyxDAIQEkmbcIVPKn2HGlS5Q45RWofzcUeiGpbBSHZQp9Z2PGWwma9yG8k
    LBiDv35u7yDyx3ph/Wb9ZX2BNxlf+f71I2ktq5ywrukSD/6zbRtf40iQE0JIl3jE
    If75bKfq5HVZgiW0oMkEOM8QOhjwMOhgwOEPAybDHmmmwjEvBRUjz1AbHQN6zmw0
    +0MS88ZomDhtZ0C1h7I0tEbNpFzBgfEQQznpesNmyMHouPMwomKyUcedDaTiczbE
    8/Px6piBeJgARGYeVs9+htm5yUxmMpOZjJ950jta3vf3GVKV02RGvUTLDGpc8QdS
    VRzVyp2hXFxHNXdnRK0egNrhB/sEzTyjb6Hi9LE7evMPZtxrlbWOvkFtszIKczZe
    JTf6XrQw4X76SNzRZshIw5BTx2TIwTh8NsXFeL6JPN9W/6HQFrv2DQAA
    } "4 of spades" 4 "black" 180x120
    64#{
    eJztlzF2wyAMQNW+ri1X8OvUc3Tp1Dv0FJ19NA89kKauqhAQg4QtkjhD34tsTIQ/
    IFsKyO+fv88g8s3ljcsHly8uD/Ak7TPf/3lJpZVZTpjnVMWDfyzLwnVsITmBKFXx
    iE18edVDGXmcJvIEp+AyBNczbE44gmGLD2HwEnsaPCkgl/UGQjNJl4F6zKxAazOK
    z5WiGACwSmszShxoBSWeQtXzNFCjnMVgDjqr+AwOMPF+xYRQMVnRz0UBV5uzon3a
    8ZdM6vjLMB0ZYQhwIJ7rZ7+GKdyd+ccMAriM38J/BiQ9kmI4fBFBNxqmWYduyWCz
    qFzOUGcqw6Adxr5VO8yt/EVplfQYK3fmGOagNVrW+32mF3mbTC+X0EwvJznZA2lX
    7O2VhcG8ufb23MJI8XIA1M1n5gk1c0DeUm3lJkeq3mHpu5F4hbiBl77Va66Uldnz
    F+VMcddfhenlosrmbRHGjVUnUMeZMXtcxptpkBn5Jhr5tvoDMlDOXvYNAAA=
    } "5 of spades" 5 "black" 260x120
    64#{
    eJztl0FywjAMRVWm2+IrZLriHN10xR04BescLYseSKtuXVk2YMtCMiUzDDMocYyc
    R+xE8o/ztf/9ALYjlR2VbyoHKm/wzu0znf/Z5tLazDvMc67SRj+WZaE6tUTeIcZc
    pS010eFTXqqzzTRFz3AKLhPBZjCddxlwGQZcBl2G+nIZGGFkXw0eVAahGYjKQH3N
    4ggGOebCEWMGgN5pnzNyHmjOhcm5EhTnJgZL0vWOz+AAU+dYCnComOK0905OwMuY
    iyPjrsSLO3Xi1TGKjTAR0M/n5t7vYU7ci3lKRtMNyWj60zL6vGiYK/NLMv+e77cy
    a2nUkB7quioYVZ8Fs1K8ovre6Zhr9mLWYVbSaNZ7m8E6LT3GyLEzY+VYTm9bE7C8
    XE1NSMWbyyibH6sJmBcfpiaUf5iawGs3RxMKY2rCiTHjVRhLEwCcRMyMieS+HPOX
    0NVztpk4wgyMx2dGvolGvq3+ALZph/D2DQAA
    }  "6 of spades" 6 "black" 340x120
    64#{
    eJztlztSxDAMhgVDC7pChopz0FBxB05BnaOl2AOpojXyI4llyY/dBGhQ4s1K/mLH
    tvxn8vr+9QjBPrm8cHnj8sHlDh5CfOb6y1Ms0uZwwjzHiz/4z7IsfPURF05wLl78
    4UP881w2pex+mlzPaMIu4+AwQ3EMxxl0XcYTpzCEBSNwNBmCvAEwGcjbXB0QTJyL
    0pGMX2LtCIZCHihHMDFX0HCuYiglnXb6DA0wvj5jEDMmOSTyhx2k/ZmTE568vV6h
    0856KcawEcYBEXb3Vz72I8zK/TO/ypj7vWAs3ZCMnc+CqeyLkrl5n17LnKUtNR0T
    TEUPJWPrqmROWi+XXhGUOkGbGYzwYpMDaDI8HCIog4oRU/OTDImkuJ1xRleKId2M
    nlXdjGZINWNoq71eMkol4uuD3reM19kYSZVp5OrGtHI1zkhbWyi9XJva4ktPE6gM
    /7G27K/7urbAdm9dW/YHzaa50BZY721oS0qNpras6SOyCG2mZqHbdoqNMtDrbMux
    40zXhpiRb6KRb6tvHjBX8/YNAAA=
    } "7 of spades" 7 "black" 420x120
    64#{
    eJzNlz1WwzAMxwWPFXyFPKaeg4WJO3AK5hwtAwfS1FXYslNsWbFUmpeiRHXl/F4s
    W/98vX2cn4HtK/op+nv0z+gP8MT9czz+/ZK9tZl3mOfcpC3+WZYltqmHeAei3KQt
    dcWfV3mqzh6niSzDKZgMwe0M8hzGTEw5mAzhYUyXc4PnQOaM0JxAHQvqc5ZAMMg1
    F4FgAKAP2nyQdSCDdl5ZK0EJrmKwiK4PbAYdTDpeMSFUTAnkvCjgb84lcNSLBzXq
    1TGKeRgCtHXYzP0WZuWuYRTNS0bTvGQ0zQtG1bxgVM23jK75lvkHmv8Do2le5qxp
    XqzhXvXatjsxnpztuTvW0FOLI7Wxl55d147nGnRdyzvVi1z3qE3zMjvdo/l+P2aw
    XnaLGazhhRmtYS7fWPNYHq5DzSe3tIqy+76ax/VtaKB5uLxWbWue38sMzRdmqPmV
    setFQ82jIZ96rEOYZtI648nZNBfj+SbyfFv9AGTB+m/2DQAA
    } "8 of spades" 8 "black" 500x120
    64#{
    eJzNlz9WwzAMxgWPFXyFPCbOwcLEHTgFc46WoQf6Jlbjv7EtK5ZpC8Wp60r5JbWl
    z8rL6/vXI4X26fqL62+uf7h+Rw/Bv7rzp6fY27aGD61rHPzhfmzb5kbvseFD1sbB
    H97lvp75rbp2vyxWa1iMyli6nEFYw5hxUzYqY3E7psGjwecMqpBkEFs71fdMRiCL
    H+EaZoTThSGi3mjjjKCDzmiYqBUjGD9ikETXGzqDCcafrxhjKiYZYOuyBmXOyZjI
    V/hTJV8dI7QZxhJ0HTZrn2UEre7cPsFmtiIjaZUxolYZI2q1ZQ602jD/QKtnMJJW
    +ZwlrbIYFnkWo2NyOTGVwZl4uYElanyccRMFiDs7pknnbzJoAnweQ6kIpFGMTwgt
    cm2S4pxTFKuJmK891YhdynuRjPOoGsNFOrzW3pnapzP7fapuzNSfqTo2Uw8P2zn1
    94AJ9X7MoA6pxgziszOj+OSNMKo/SA/XYR3zXdMhuPu2ekZ5lB/qGTnMQz3vN6rC
    zPWMriRzPedaq+fLDvVMVtXY3zK8rgsMFMlf4x0kMjPvRDPvVt855Lq+9g0AAA==
    } "9 of spades" 9 "black" 580x120
    64#{
    eJzNlztWxDAMRQ2HFrSFHCrWQUPFHlgFdZaWYhb0KlrjXxJLVmzNwJkZJ05G9p34
    o2c5ef/8eXYpfYf8FvJHyF8hP7inVD6H+tNLzjzN6XTznG/xCD+WZQn3WOLT6bzP
    t3jEonB5lY9q0uM0+VHCREPGu78zue9dBs7RkCHckGE4qQxc/QSnMq5+ZjYkg+Rz
    bkgmulgaYg6RdCAN7ousFVKMsxgU0bXGmIHKgDGxvmKI8qqgykApWbvpCbl+N3zq
    eeOvMp7ir9Si9Be2ySON2WeG+bBhwCYm/AWgEcPGXjdVC8nCrNw5TJ4zIDGkMkhz
    T/DZCSpTmihX0e992L54kS+AvuYFo2ueM3eg+QuYInNpKJpvjUGMahglRjXMYboR
    Y+nzeOyGObT44pra+C89m9aOZQ2a1rIlJphiiyVG7YO+MB6yfo+ZJoYXJsX7PgNW
    qDRVM8re1DDKHrf3x+VdUdsrVwZlc9X2XDb2o717ZeDvSfPIo+xqHrTO7LHm01uZ
    19/ZJNOLURvTiVEbo7yLtsxRujojRa4wkCJvmG61nbF8E1m+rX4BEApi6fYNAAA=
    } "10 of spades" 10 "black" 20x140
    64#{
    eJzNlz+OEzEUxh+IxvKyvsKKinPQUHEHTkGdI7i1UuwV3EbTbEG/V3kVEtIK833P
    9mTsTNiVFgmcTDKZ+fnz+2d78unLj/di7RuOjzg+4/iK4428s+sH3P9+W4+xHewt
    h0P94gsnDw8P+OaVYm8ppX7xxUv4+DBLXbS3d3fluaZ34VmmyOsZ5f2/wBR5gT3/
    iLmShrBlupoml5yLMcVYNJRgmd0wyhMXK+NC8fmSsVNN4kSiQObkQ6hVsjJqtaKp
    ttAYBvrMmIkkm7WVKVub7SbOPFqwTmbPwLTelVEqmF/lnNPQxiia0YB0HZUrDC48
    rn6dbe6mdkarjm59nxkOE+rlsMs8Kaw2nZlRXRkpLT4Dg3gqDFTfxmoMPLzIl1+A
    LOZKi3MoU750QQTJlL34tPLJhOS+1PVgzldlFsnZu3T/BNPg+i7joZNdSlJXlx2m
    F0b1WPZ01ELUGYTlkgEiFkJrytjNjDrInHV4HgcGUZYUoZN6PYOKbmSyw1xA32M6
    8R1hCybIyCyEqHP0x6OnZPDzWIKwUCfl03LKJ5qT8zSWXxapOqCOMbME/DiWpxA7
    B7oMt3JAVvLELBQiZS2zk020rV+4RCGpQsitk0lHsq9CraGL00nHQdtqq9cIhJJM
    9phBuUcZfmUKjToZ3vJozVehUQfxpUHwvah68WbRFOcTDeI9xfxE/NDBc6XaMi5m
    PzjGsSdGflL+bDQ98GmKswnl7jwmFw0amYVCMji2yBxnxt7nllLkZGGXKYZMouSe
    UxPKc04zq0FaSknByYmBDjpnqzArfgv1mAsIobOrlWpFS6FRJ3qaeq54TCXMgynO
    nLnSZw50osy14YSzxfUos/ijlHmseSI7bh7jfLfIbRYErnEzw0VCNjpSdhiVDSMV
    mRi1rYGrHFa7pSGzToEJNyUlz1Uz7zK2O+I8co/LFwztC7ou3LaMtx14ZX7d4od3
    2X5wv1iWvgGsixy3jcpwVSQjbYdZmbrQVkZv6h7Xd6qRoT1I0NPK1MsDw03EFuld
    xrZV09GrjG3P1Z7HzvSdfC8+Qa4w9YnBQib9caMONee0x7ky7QllZGqcz4VhMmVj
    c2daPcdSpnzp6hfmQsQEcXEd4Jwv7q60pz6s4anNglkuGPilfOIDk6rQWGMarj9D
    bpg/tv+R6bPytcxz7UXMS/4TveS/1W9aPkPU9g0AAA==
    } "jack of spades" 11 "black" 100x140
    64#{
    eJzNlz1y3DgQhXtdTlAou6+gcrTn2GQj32FP4VhHQNqlgFdAOoVgFeyBELmKibHv
    NTg0CXA1CjYwR9RQmG9e/7JB/fH1+yfx4xvO33H+ifMvnL/JR19/xuf/fO7n+Xj2
    H3l+7m984eL19RXvXGn+I631N764hF9fRqnp+PD01B4d9UkfMk3+B0YAPGCqR/iA
    0fbLMv9RBj0ym1qNJcdQa1XpyyNTqVdyCVWVqcPCxPhyKWCaQKkzhxyqpxQXEbYU
    H246x1qou61kSlBtNfBaodPaT4aqQlvZuB6kM3pktliLgJEqmpzpFjam9oTUGIPB
    mZ2hsYFB8GFRfBlMLfns857YbKltTM7n2HcmWHXGlDq+dsG4E7Iq/RmZWsnY4itX
    DKKtvCuDJXjC4NW/AmyqF9xBivmV5HH9+NzGeoktkpliJkiG/Gz9E0xKjCI5bjrt
    WIt14V8QQ+VBFcQwMmsKC2QYR8k4i+fuxIjFbC2bQSlGFB+choEJ0KlRLQGCRzG7
    TycmyApE8koIOjAVbyOTVWORqKshrx5atCgnJnZfU250KdLtK4YYmhDWtIc2Md7Q
    lrItsIZUi9yWmfGOziiGJYYVb+mCqWYMGdbk9gJ3dGAQF1sDq1UgBKZYHRn2Ukgs
    uYQVDEyNOuJxUUZXUdgslkadiCplyrD3ZIXHppNOKbHsa8EMMrNOYd6OI+xCB8t2
    PGYd7yo9vTyEE8N76fxiYQcGt+DZFjt70tGTzwwiXuvsfyMVZdYJViTULY05IV2z
    jr1gXi4SOJG3nht1xEo09KGw3bLRuzDqrC+ZzJK6DHvlNuoE9lUOtiLqEjjx2uQP
    PJai3qvezzozahm3aMbkaGz9JDIz4SXznoEQrOWt5ybGcysQWmpOjl8wlbNA0WFs
    y4KBeI5LY+AY4DCosFZzlGYtDIxk7UMFhbCFiFg9M4Etk6ETfY6nv9fkO8eRYR19
    XHhbc/eCThv6Odd9WmpzI9ZmBk5gFMCj1frOfWA4pmlrEZ8wGUNRtx14Z3zcUwfL
    yslo+0Z7Z/q2wc+5j7aaVtt3mDvTb0qYCT0dMNXuO9UVQ9ewZbT9fj4y8Kf1zbau
    F0zfVnNutTPononp23O5MxxQ7b6Tn/PD1Cy8z4PFqBPDq41B8BiKcTOmx1pIZ9Q3
    1FTKZuxYr9qZxgcsDZiX+5POyWf1By20Zwuo/H0HbqfYO4MnrLpyF5zr1e4MhGSF
    LTK1TQyv8cRXoYM7SIf+6Y9318eJefP4BRmvzNtMu4f4FvPweBfznv+J3vO/1b/O
    39P79g0AAA==
    } "queen of spades" 12 "black" 180x140
    64#{
    eJytVzuS1DAQbSgSlYrtK2wRcQ4SIu7AKYjnCE5dE3AFpVNKNuBAHREi3mtZXkv2
    4F1AXtuz9pvXrz9qaT59+flefHzD+RHnZ5xfcb6Rd/78gvc/HurZj4v/yeVSbzzw
    4enpCXc+Kf4npdQbDz7C5cNItRtvHx/L2bBHPcUU+XeMiIn+B4xH4Axjeo7xwJ5h
    7FU8d9KgWz28Wl5HI9etX36VxAFI4jf8semAsYkDMP7nL3QbQ/FrmDEA+053ZIfR
    VTlQ9aMu9KtfLsGVeynyKkcYhavFMBZGLZWqw9C6EGM1PACYDLYob8CQbbCl1VYL
    s7q6E4xfX4+BwoYxu4OBPqualUDmSzs9YuAhxGTNV/n1sPXdqKdUW9ryNcTH3JY6
    ka310+XCohDEGMKqHGJUEtWpW+LnA8wEoinBjDJptygHGCFR8oBIihIXzR1mBlGI
    BKSEe4pxXz9CY3iXRMKUJtxm2fHMMMZXOc+44Yg3HXmCC5qzRGBACKK046ExYMAD
    YRhyvfUYjCZaJPoBwh4Tc6qiWfbgy3Tv2mEiZFBQRE1YoRo4GHKPSZYXDAb8I08c
    9CjjQedpK1WeUQ+nMJ8nzkIE+UAPJxK+7GHxjLqe0XefdzGw/WTyMGXzwMMo54WH
    eQdVkJEHmXJb0HWLEV6J9TmFywwcbBFDHhSUDTxBSL/aiqw37XnCVRahVbN3jYEn
    3NzbZ81rj9rkYhZ23bjEkHgGdbTlUZaaCxRimIaGWkImT0o+e5h3DUxzb6vqea4N
    YFBTB3oYuFpjCOCU817PUqGeNmJqG9/qmVjmIYZrzjfOC4Nfw7y4TR6WNF8l3ji/
    FJHvMUrHfH5dc/J61DDyqIXJX80+46vokQfOsKoCW8IiesfDlaDlXVub2WHAhOpB
    c1WfPTS2x4ApedNE3ap599tjADKSsHUqgm1HmLI0XmWHZf5XjMmiWUptz/6lHsN2
    31brdTGA1Q3Glw1tq4hD3FbZYNaFylejZdkpdzD+oerRv8D4snqC8eVZ22rtkkle
    yi4+W4xvRHqeumNo+6Y1hj2m1o+smxF/Xg4wy26GpS5tRmwa6mLdCbBD4kapdjkb
    MfVbrFXfcm3KQeq2zOuw1L2d79yqlGeMtUbDNaGN1GPkD3vIlotGcm8QcwJ5KY+e
    ES1+nWA8hv8BcyLmpZiX/CZ6yW+r3/c0VV/2DQAA
    } "king of spades" 13 "black" 260x140
    64#{
    eJztlzEOAiEQRdHYkKzDFYiV57Cx8g6ewpoj0O5tKCw5BAfYbLayxZmhUpMB+xl4
    y4b9gc3r5nJ7HQ3XAzkjV+SO7MyB9wN+f0LjswJPE0JbaOBLSglX2qk8Ta1toUFb
    +Dh9H/VTe+9rrzbvuplqXO1chQG3WcggYCgzxyVJcAYmK8DnxLXMApEyE9hZgDMZ
    b5WgDGQLAvw/ZUmrQFE/6kf9qB/1o37Uj/pRP+pH/fzlZ6QHGehlRnqikd7qDVAb
    l+X2DQAA
    } "card back side" 0 "none" -200x-200
]

Freecell:

Rebol [title: "Playing Card Framework"]

do %cards.r

random/seed now
loop 156 [
    pos1: pick cards rnd1: (random 52) * 5
    pos2: pick cards rnd2: (random 52) * 5
    poke cards rnd1 pos2
    poke cards rnd2 pos1
]

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            start-coord: face/offset
            face/data: event/offset
            remove find face/parent-face/pane face
            append face/parent-face/pane face
        ]
        if find [over away] action [
            unrounded-pos: (face/offset + event/offset - face/data)
            snap-to-x: (round/to first unrounded-pos 80) + 20
            snap-to-y: (round/to second unrounded-pos 20) + 20
            face/offset: (as-pair snap-to-x snap-to-y)
        ]
        if action = 'up [
            if any [
                (find cards face/offset)
                (face/offset/2 < 20)
            ] [
                if (face/offset/2 < 398) [face/offset: start-coord]
            ]
            replace cards start-coord face/offset
            arrange-cards
        ]
        show face
    ]
]

positions: does [
    temp: copy []
    foreach item cards [if ((type? item) = pair!) [append temp item]]
    return sort temp
]

arrange-cards: does [
    foreach position positions [
        foreach card system/view/screen-face/pane/1/pane [
            if (card/offset = position) and (position/2 < 398) [
                remove find system/view/screen-face/pane/1/pane card
                append system/view/screen-face/pane/1/pane card
            ]
        ]
    ]
    show system/view/screen-face/pane/1/pane
]

gui: [size 670x510 backdrop 0.150.0 across ]
foreach [card label num color pos] cards [
    append gui compose [
        at (pos) image load to-binary decompress (card) feel movestyle
    ]
]

box-pos: 18x398
loop 4 [
    append gui compose [
        at (box-pos) box green 72x2
        at (box-pos) box green 2x97
        at (box-pos + 320x0) box white 72x2
        at (box-pos + 320x0) box white 2x97
    ]
    box-pos: box-pos + 80x0
]

view/new center-face layout gui
arrange-cards
do-events

I added the following code to the 'up action block of the movestyle code, to determine which cards are in the same column as a moved card:

column-coords: copy []

; The loop below checks every coordinate in the card data block.  If
; the horizontal position is the same as the card being put down,
; append that coordinate to the "column-coords" block:
foreach item cards [
    if ((type? item) = pair!) [
        if item/1 = face/offset/1 [
            append column-coords item
        ]
    ]
]

column: copy []

; Go through the coordinates collected above, find the name of the
; card at each coordinate, and add each name to the "columns" block:

foreach item (sort copy column-coords) [
    append column (pick cards ((index? find cards item) - 3))
]

print column

7.19 Case 19 - Downloading Directories - A Server Spidering App

This program was created to download files from a computer running the Aprelium web server (http://aprelium.com).

REBOL [title: "Directory Downloader"]

avoid-urls: [
    "/4/DownLoad/en_wikibooks_org/skins-1_5/common/&"
    "DownLoad/groups_yahoo_com/group/Join%20This%20Group!/"
    "DownLoad/pythonide_stani_be/ads/"
    "Nick%20Antonaccio/Desktop/programming/api/ewe/"
]

copy-current-dir: func [
{
    Download the files from the current remote directory
    to the current local directory and create local subfolders
    for each remote subfolder.  Then recursively do the same
    thing inside each sub-folder.
} 
    crfu  ; current-remote-folder-url
    clf   ; current-local-folder
    lrf   ; local-root-folder
] [
    ; Check that the URL about to be parsed is not in the avoid
    ; list above.  This provides a way to skip specified folders
    ; if needed:

    foreach avoid-url avoid-urls [
        if find crfu avoid-url [return "avoid"]
    ]

    ; First, parse the remote folder for file and folder names.
    ; Given the URL of a remote page, create 2 list variables.
    ; files:  remote files to download (in current directory)
    ; folders:  remote sub-directories to recurse through.
    ; There's an error check in case the page can't be read:

    if error? try [page-data: read crfu] [
        write/append %/c/errors.txt rejoin [
            "error reading (target read error): " 
            crfu newline]
        return "index.html"
    ]

    ; if the web server finds an index.html file in the folder
    ; it will serve its contents, rather than displaying the 
    ; directory structure.  Then it'll try to spider the HTML
    ; page.  The following will keep that error from occuring.
    ; NOTE:  this error was more effectively stopped by 
    ; editing the index page names in the Abyss web server:
    if not find page-data {Powered by <b><i>Abyss Web Server} [
        ; </i></b>
        write/append %/c/errors.txt rejoin [
            "error reading (.html read error): " 
            crfu newline]
        return "index.html"
    ]
    files: copy []
    folders: copy []
    parse page-data [
        any [
            thru {href="} copy temp to {"} (
                last-char: to-string last to-string temp
                either last-char = "/" [
                ; don't go upwards through the folder structure:
                    if not temp = "../" [
                        append folders temp
                    ]
                ][
                    append files temp
                ]
            )
        ] to end
    ]

    ; Next, download the files in the current remote folder
    ; to the current local folder:

    foreach file files [
        if not file = "http://www.aprelium.com" [
            print rejoin ["Getting: " file]
            new-page: rejoin [crfu "//" file]
            replace new-page "///" "/"
            if not exists? to-file file [
                either error? try [read/binary to-url new-page][
                    write/append %/c/errors.txt rejoin [
                        "There was an error reading:  "    new-page
                        newline]
                ] [
                if error? try [
        write/binary to-file file read/binary to-url new-page
                ][
                    write/append %/c/errors.txt rejoin [
                    "error writing: " 
                    crfu newline]]
                ]
            ]
        ]
    ]

    ; Check to see if there are no more subfolders.  If so,
    ; exit the copy-current-dir function

    if folders = [] [return none]

    ; Define the recursion pattern.  This changes into the 
    ; current local folder, and runs the copy-current-dir 
    ; function (the current function we are in), which itself
    ; contains the recurse function, which itself will call
    ; the copy-current-dir, etc.  The effect of this recursion
    ; is that every subfolder of every folder is entered.  
    ; This is what enables the spidering:

    recurse: func [folder-name] [
        change-dir to-file folder-name
        crfu: rejoin [crfu
            folder-name]
        clf: rejoin [clf
            folder-name]
        copy-current-dir crfu clf lrf
        ;  When done, go back up a folder on both the local
        ;  and remote machines.  The replace actions remove
        ;  the current folder text from the end of the current
        ;  folder strings.
        change-dir %..
        replace clf folder-name ""
        replace crfu folder-name ""
    ]

    ; Third, create local folders to mirror each directory in
    ; the current remote folder, and then spider down through
    ; them using the recurse function to download all the files
    ; and subdirectories included in each folder:

    foreach folder-name folders [
    ;    foreach avoid-url avoid-urls [
    ;        if not find folder-name avoid-url [
                make-dir to-file folder-name
                recurse folder-name
    ;        ]
    ;    ]
    ]
]

; Now, get initial remote URL and create a local folder to
; mirror it, with a nicely formed name (only allowable Windows
; file name characters).

    initial-pageurl: to-url request-text/default trim {
        http://192.168.1.4:8001/4/}
    initial-local-dir: copy initial-pageurl
    replace initial-local-dir "http://" ""
    replace/all initial-local-dir "/" "_"
    replace/all initial-local-dir "\" "__"
    replace/all initial-local-dir ":" "--"
    lrf: to-file rejoin [initial-local-dir "/"]
    if not exists? lrf [make-dir lrf]
    change-dir lrf
    clf: lrf

; Start the process by running the copy-current-dir function:

copy-current-dir initial-pageurl clf lrf

print "DONE" halt

7.20 Case 20 - Vegetable Gardening

add_veggie.r:

REBOL [title: "Add Veggie"]

; read the current database:
veggies: copy load %database.db
; get the list of veggies (the 1st item in each block):
veggie-list: copy []
foreach veggie veggies [append veggie-list veggie/1]

; create a GUI with the appropriate fields and text-lists:
view/new center-face add-gui: layout [
    across
    text "new vegetable:" 88x24 right new-veg: field
    return
    text "compatible:" 88x24 right 
    new-compat: text-list data veggie-list
    return
    text "incompatible:" 88x24 right 
    new-incompat: text-list data veggie-list
    return
    text "note 1:" 88x24 right new-note1: field
    return
    text "note 2:" 88x24 right new-note2: field
    return
    text "note 3:" 88x24 right new-note3: field
    return

    ; now add a button to run the foreach loops:

    tabs 273 tab btn "Done" [
        ; First, append the new veggie data block to
        ; the existing database block.  Create the new
        ; block from the text typed into each field,
        ; and from the items picked in each of the 
        ; lists above ("reduce" evaluates the listed
        ; items, rather than including the actual text.
        ; i.e., you want to add the text typed into the
        ; new-veg field, not the actual text 
        ; "new-veg/text").  "append/only" appends the
        ; new block to the database as a block, rather
        ; than as a collection of single items:
        append/only veggies new-block: reduce [
            new-veg/text
            ; "reform" creates a quoted string from the
            ; block of picked items in the text-lists:
            reform new-compat/picked
            reform new-incompat/picked
            new-note1/text
            new-note2/text new-note3/text
        ]
        ; Now loop through the compatibility list of the
        ; new veggie, and add the new veggie to the 
        ; compatibility lists of all those other
        ; compatible veggies.  I put a space in if there
        ; were already other veggies in the list:
        foreach onecompat new-compat/picked [
            foreach veggie veggies [
                if find veggie/1 onecompat [
                    either veggie/2 = "" [spacer: ""] [
                        spacer: " "]
                    append veggie/2 rejoin [spacer 
                    new-veg/text]
                ]
            ]
        ]
        ; Now do the same thing for the incompatibility
        ; list:
        foreach oneincompat new-incompat/picked [
            foreach veggie veggies [
                if find veggie/1 oneincompat [
                    either veggie/3 = "" [spacer: ""] [
                        spacer: " "]
                    append veggie/3 rejoin [spacer 
                    new-veg/text]
                ]
            ]
        ]
        save %database.db veggies
        ; start the veggie data editor again when done:
        launch %veggie_data_editor.r
        unview add-gui
    ]
]
focus new-veg
do-events

remove_veggie.r:

REBOL [title: "Remove Veggie"]

veggies: copy load %database.db
remove-veggie: read %veggie2remove.r

; remove the selected veggie from compatible lists (the second
; field in each block).  This is done by replacing any 
; occurrence of the remove-veggie with an empty string ("").
; That effectively erases every occurrence of the veggie:

foreach veggie veggies [
    replace veggie/2 remove-veggie ""
]

; do the same thing to the incompatible lists of all other
; veggies (field 3 in each block):

foreach veggie veggies [
    replace veggie/3 remove-veggie ""
]

save %database.db veggies
; start the veggie data editor again when done:
launch %veggie_data_editor.r

veggie_gui.r:

REBOL [title: "Veggie Data Editor"]

evt-close: func [face event] [
    either event/type = 'close [
        inform layout [
            across
            btn "Save Changes" [
                ; when the save btn 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
                launch "veggie_gui.r"
                quit
            ]
            btn "Lose Changes" [
                launch "veggie_gui.r"
                quit
            ]
            btn "CANCEL" [hide-popup]
        ] none ] [ 
        event
    ]
]
insert-event-func :evt-close

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 not exists? %database.db [write %database.db {[][]}]
database: load %database.db

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: [Vegetable Yes No Note1 Note2
            Note3]
        data: copy database
        tri-state-sort: false
        editable?: true
    ]
    across
    btn "add veggie" [
        launch %add_veggie.r
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
        quit
    ]
    btn "remove veggie" [
        if (to-string request-list "Are you sure?" 
                [yes no]) = "yes" [
            first-veg: copy first theview/get-row
            theview/remove-row
            write %veggie2remove.r first-veg
            launch %remove_veggie.r
            backup-file: to-file rejoin ["backup_" now/date]
            write backup-file read %database.db
            save %database.db theview/data
            quit
        ]
    ]
    btn "filter veggies" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        theview/filter-string: filter-text
        theview/update
    ]
    btn "upload to web" [
        uurl: ftp://user:pass@website.com/public_html/path/
        if error? try [
            ; first, backup former data file:
            write rejoin [
                uurl "database_backup.db"] read rejoin [
                uurl "database.db"]
            write rejoin [uurl "database.db"] read %database.db
            alert "Update complete."
        ] [alert "Error - check your Internet connection."]
    ]
]

command line version:

REBOL [title: "Veggies"]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print rejoin ["Veggie:   " veggie/1]
        print a-line
        print rejoin ["Matches:  " veggie/3]
        print rejoin ["No-nos:   " veggie/2]
        print rejoin ["Note 1:   " veggie/4]
        print rejoin ["Note 2:   " veggie/5]
        print rejoin ["Note 3:   " veggie/6]
        print newline
    ]
] 
forever [
    prin "^(1B)[J"
    print "Here are the current foods in the database:^/"
    print a-line
    foreach veggie veggies [prin rejoin [veggie/1 "  "]]
    print "" print a-line
    print "Type a vegetable name below.^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What food would you like info about?  }
    print newline
    switch/default answer [
        "all"     [print-all]
        ""         [ask "^/Goodbye!  Press [Enter] to end." quit]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 answer [
                print a-line
                print rejoin ["Veggie:   " veggie/1]
                print a-line
                print rejoin ["Matches:  " veggie/3]
                print rejoin ["No-nos:   " veggie/2]
                print rejoin ["Note 1:   " veggie/4]
                print rejoin ["Note 2:   " veggie/5]
                print rejoin ["Note 3:   " veggie/6]
                print newline
                found: true
            ]
        ]
        if found <> true [
            print "That vegetable is not in the database!^/"
        ]
    ]
    ask "Press [ENTER] to continue"
]    
halt

cgi version:

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "Veggies"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Veggies"</TITLE></HEAD><BODY>]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print [<BR>]
        print rejoin ["Veggie:   " veggie/1]
        print [<BR>]
        print a-line
        print [<BR>]
        print rejoin ["Matches:  " veggie/3]
        print [<BR>]
        print rejoin ["No-nos:   " veggie/2]
        print [<BR>]
        print rejoin ["Note 1:   " veggie/4]
        print [<BR>]
        print rejoin ["Note 2:   " veggie/5]
        print [<BR>]
        print rejoin ["Note 3:   " veggie/6]
        print [<BR>]
    ]
]

print "Here are the current foods in the database:^/"
print [<BR>]
print a-line
print [<BR><strong>]
foreach veggie veggies [prin rejoin [veggie/1 "  "]]
print ""
print [</strong><BR>]
print a-line
print [<BR>]

submitted: decode-cgi system/options/cgi/query-string
if submitted/2 <> none [
    switch/default submitted/2 [
        "all"     [print-all]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 submitted/2 [
                print a-line
                print [<BR>]
                print rejoin ["Veggie:   " veggie/1]
                print [<BR>]
                print a-line
                print [<BR>]
                print rejoin ["Matches:  " veggie/3]
                print [<BR>]
                print rejoin ["No-nos:   " veggie/2]
                print [<BR>]
                print rejoin ["Note 1:   " veggie/4]
                print [<BR>]
                print rejoin ["Note 2:   " veggie/5]
                print [<BR>]
                print rejoin ["Note 3:   " veggie/6]
                found: true
            ]
        ]
        if found <> true [
            print [<BR>]
            print "That vegetable is not in the database!"]
            print [<BR>]
    ]
]

print [<FORM ACTION="http://website.com/rebol/veggie.cgi">]
print [<BR><HR><BR>"Enter a veggie you'd like info about:"<BR>]
print ["Veggie: "<INPUT TYPE="TEXT" NAME="username" SIZE="25">]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

Final cgi example with an HTML dropdown box:

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "Veggies"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Veggies"</TITLE></HEAD><BODY>]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print [<BR>]
        print rejoin ["Veggie:   " veggie/1]
        print [<BR>]
        print a-line
        print [<BR>]
        print rejoin ["Matches:  " veggie/3]
        print [<BR>]
        print rejoin ["No-nos:   " veggie/2]
        print [<BR>]
        print rejoin ["Note 1:   " veggie/4]
        print [<BR>]
        print rejoin ["Note 2:   " veggie/5]
        print [<BR>]
        print rejoin ["Note 3:   " veggie/6]
        print [<BR>]
    ]
]

submitted: decode-cgi system/options/cgi/query-string
if submitted/2 <> none [
    switch/default submitted/2 [
        "all"     [print-all]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 submitted/2 [
                print a-line
                print [<BR>]
                print rejoin ["Veggie:   " veggie/1]
                print [<BR>]
                print a-line
                print [<BR>]
                print rejoin ["Matches:  " veggie/3]
                print [<BR>]
                print rejoin ["No-nos:   " veggie/2]
                print [<BR>]
                print rejoin ["Note 1:   " veggie/4]
                print [<BR>]
                print rejoin ["Note 2:   " veggie/5]
                print [<BR>]
                print rejoin ["Note 3:   " veggie/6]
                found: true
            ]
        ]
        if found <> true [
            print [<BR>]
            print "That vegetable is not in the database!"]
            print [<BR>]
    ]
]

print [<FORM ACTION="http://website.com/rebol/veggie.cgi">]
print [<BR>"Please select a veggie you'd like info about:"<BR>]
print ["Veggie: "<select NAME="username"><option>"all"]
foreach veggie veggies [prin rejoin ["<option>" veggie/1]]
print [</option>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

Main script:

REBOL [title: "Veggie Matches"]

load-data: does [
    veggies: copy load %database.db
    veggie-list: copy []
    foreach veggie veggies [append veggie-list veggie/1]
    veggie-list: sort veggie-list
]

load-data

view display-gui: layout [
    h2 "Click a veggie name to display matches and other info:"
    across
    list-veggies: text-list 200x400 data veggie-list [
        current-info: []
        foreach veggie veggies [
            if find veggie/1 value [
                current-info: rejoin [
                    "COMPATIBLE:     " veggie/3 newline newline
                    "INCOMPATIBLE:   " veggie/2 newline newline
                    "NOTE 1:   " veggie/4 newline newline
                    "NOTE 2:   " veggie/5 newline newline
                    "NOTE 3:   " veggie/6
                ]
            ]
        ]
        display/text: current-info
        show display show list-veggies
    ]
    display: area "" 300x400 wrap
    return
    btn "Edit Tables" [
        do %veggie_data_editor.r
        ; launch "veggie_data_editor.r"
        ; load-data 
        ; show list-veggies
        ; show display
    ]
]

7.21 Case 21 - An Additional Teacher Automation Project

1:

; The "url" variable below comes from the multiuser framework
; borrowed from the scheduler app:
students: read/lines rejoin [url "/schedule.txt"]
; Initialize some other variables:
other-additions: []    other-deductions: [] prepays: [] 
pay-for: copy [] online: copy []

view center-face layout [
    h2 "Local Students:"
    ; "face/picked" refers to the currently selected items in
    ; the text-list (use [Ctrl] + mouse click to select multiple
    ; items, and assign that to the variable "pay-for":
    text-list data copy students [pay-for: copy face/picked]
    h2 "Other Additions:" 
    field [
        ; add the entered info to the text-list, and update
        ; the display:
        append other-additions copy face/text
        show other-additions-list
    ]
    other-additions-list: text-list 200x100 data other-additions [
        ; remove any entry when selected by the user, and update
        ; the display
        remove-each item other-additions [item = value]
        show other-additions-list
    ]
    at 250x20
    h2 "Online Students:"
    text-list data copy students [online: face/picked]
    h2 "Other Deductions:"
    field [
        append other-deductions copy face/text
        show other-deductions-list
    ]
    other-deductions-list: text-list 200x100 data other-deductions [
        remove-each item other-deductions [item = value]
        show other-deductions-list
    ]
    at 480x20
    h2 "Prepaid Lessons:"
    prepay-list: text-list data prepays [
        remove-each item prepays [item = value]
        show prepay-list
    ]
    ; I still need to create the prepay.r program:
    btn 200x30 "Calculate Prepaid Lessons" [
        save %prepay.txt load rejoin [url "/prepay.txt"]
        do %prepay.r
    ]
    at 480x320
    btn 200x100 font-size 17 "Calculate Total Fees" [
        total-students: (
            (length? pay-for) - (length? online) + 
            (length? other-additions) - (length? other-deductions) -
            (length? prepays)        
        )
        ; I want to create an HTML output for this section:
        alert rejoin ["Total: " to-string total-students]
    ]
]

An example of the block structure I came up with to store data in the prepay.txt file:

[
    ; name:
    "John Smith"

    ; prepayment amounts and dates:
    [ [$100 4-April-2006] [$100 5-May-06] ]

    ; dates of lessons:
    [
        [$20 4-April-06] [$20 11-April-06] [$20 18-April-06]
        [$20 25-April-06] [$20 5-May-06]
    ]
]

[
    "Paul Brown" 

    [ [$100 4-April-2006] ]

    [
        [$20 4-April-06] [$20 25-April-06]
    ]
]

[
    "Bill Thompson"

    [ [$200 22-March-2006] ]

    [
        [$20 22-March-06] [$20 29-March-06] [$20 5-April-06]
        [$20 12-April-06] [$20 19-April-06]    [$20 26-April-06]
        [$20 3-May-06]
    ]
]


[
    ; name:
    "John Smith"

    ; prepayment amounts and dates:
    [ "$100 4-April-2006" "$100 5-May-06" ]

    ; dates of lessons:
    [
        "$20 4-April-06" "$20 11-April-06" "$20 18-April-06"
        "$20 25-April-06" "$20 5-May-06"
    ]
]

[
    "Paul Brown" 

    [ "$100 4-April-2006" ]

    [
        "$20 4-April-06" "$20 25-April-06"
    ]
]

[
    "Bill Thompson"

    [ "$200 22-March-2006" ]

    [
        "$20 22-March-06" "$20 29-March-06" "$20 5-April-06"
        "$20 12-April-06" "$20 19-April-06" "$20 26-April-06"
        "$20 3-May-06"
    ]
]

Prepayment Calculator:

REBOL [title: "Prepayment Calculator"]

prepays: load rejoin [url "/prepay.txt"]
names: copy []
prepay-history: []
lesson-history: []
display-todays-bal: does [
    ; calculate and display the current balance for the
    ; selected student:    
    todays-balance: $0
    foreach payment prepay-history [
        todays-balance: todays-balance + (
            first (to-block payment)
        )
    ]
    foreach lesson-event lesson-history [
        todays-balance: todays-balance - (
            first (to-block lesson-event)
        )
    ]
    ; update the display of today's balance for the
    ; selected student :
    today-bal/text: to-string todays-balance
    show today-bal
]
foreach block prepays [append names first block]
view center-face gui: layout [
    across
    text bold "New Prepayment:"
    text right "Name:" new-name: field
    text right "Date:" new-date: field 125 to-string now/date
    text right "Amount:" new-amount: field 75 "$"
    btn "Add" [
        create-new-block: true
        foreach block prepays [
            if (first block) = new-name/text [
                create-new-block: false
                append (second block) to-string rejoin [
                    new-amount/text " " new-date/text
                ]
            ]
        ]
        if create-new-block = true [
            new-prepay: copy []
            append new-prepay to-string new-name/text
            append new-prepay to-string rejoin [
                new-amount/text " " new-date/text
            ]
            append prepays new-prepay
            names: copy []
            foreach block prepays [append names first block]
        ]
        display-todays-bal
        show existing show pre-his show les-his show today-bal
    ]
    return 
    text bold underline "Edit Data Manually" [
        view/new center-face layout [
            new-prepays: area 500x300 mold prepays
            btn "Save Changes" [
                prepays: copy new-prepays/text 
                unview
            ]
        ]
        names: copy []
        foreach block prepays [append names first block]
        show gui
        show existing show pre-his show les-his show today-bal
    ] 
    return
    text "Existing Prepayments:"  pad 75
    text "Prepayment History:"  pad 85
    text "Lesson History:" pad 100
    text "Balance:"
    return
    existing: text-list data names [
        ; When a name is selected from this text list, update
        ; the other fields on the screen:
        new-name/text: value
        show new-name
        foreach block prepays [
            if (first block) = value [
                ; update the other text lists to show the
                ; selected student's prepay and lesson history:
                prepay-history: pre-his/data: second block
                show pre-his
                lesson-history: les-his/data: third block
                show les-his
            ]
        ]
        display-todays-bal
        ; get the list of selected students
        prepaid-today: copy face/picked
    ]
    pre-his: text-list data prepay-history
    les-his: text-list data lesson-history
    today-bal: field 85
    return
    btn "Apply Selected Prepayments Today" [
        save %prepaid.txt prepaid-today

        unview
    ]
]

In the original scheduling outline, I replace all references in the code to "schedule.txt" with "prepay.txt":

REBOL [title: "Payment Calculator"] 

error-message: does [
    ans: request {Internet connection is not available.
        Would you like to see one of the recent local backups?}
    either ans = true [
        editor to-file request-file quit
    ][
        quit
    ]
]

if error? try [
    teacherlist: load ftp://user:pass@website.com/teacherlist.txt
][
    error-message
]
teachers: copy []
foreach teacher teacherlist [append teachers first teacher]
view center-face layout [
    text-list data teachers [folder: value unview]    
]

pass: request-pass/only
correct: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (pass = (second teacher)) [
        correct: true
    ]
]
if correct = false [alert "Incorrect password." quit]

url: rejoin [http://website.com/teacher/ folder]
ftp-url: rejoin [
    ftp://user:pass@website.com/public_html/teacher/ folder
]

if error? try [
    write %prepay.txt read rejoin [url "/prepay.txt"]
][
    error-message
]

; backup (before changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
; local:
write to-file rejoin [
    folder "-prepay_" now/date "_" cur-time ".txt"
] read %prepay.txt
; online:
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %prepay.txt
][
    error-message
]

editor %prepay.txt

; backup again (after changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
write to-file rejoin [
    folder "-prepay_" now/date "_" cur-time ".txt"
] read %prepay.txt
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %prepay.txt
][
    alert "Internet connection not available while backing up."
]

; save to web site:
if error? try [
    write rejoin [ftp-url "/prepay.txt"] read %prepay.txt
][
    alert {Internet connection not available while updating web
    site.  Your schedule has NOT been saved online.}
    quit
]    
browse url

8. Other Scripts

This section contains various random scripts that you might find useful.

The first script provides a quick visual reference of all REBOL's built in colors:

REBOL [Title: "Quick Color Guide"]

echo %colors.txt ? tuple! echo off
lines: read/lines %colors.txt
colors: copy []
gui: copy [across space 1x1]
count: 0
foreach line at lines 2 [
    if error? try [append colors to-word first parse line none][] 
]
foreach color colors [
    append gui [style box box [alert to-string face/color]]
    append gui reduce ['box 110x25 color to-string color]
    count: count + 1
    if count = 5 [append gui 'return count: 0]
]
view center-face layout gui

This next quick script demonstrates how to convert REBOL color tuples to HTML colors, and visa-versa:

to-binary request-color
to-tuple #{00CD00}
view layout [box to-tuple #{5C743D}]  ; view an HTML color on screen!

This is a quick way to review the console history of your current REBOL session:

foreach line reverse copy system/console/history [print line]

Here's how to remove the last 2 items from a block:

x: ["asdf" "qwer" "zxcv" "uiop" "hjkl" "vbnm"]
y: head clear skip tail x -2
probe y

The script below demonstrates how to use email ports to read one message at a time from a pop server. Be sure to set your email user account settings before running this one (that's explained earlier in this tutorial):

for i 1 length? pp: open pop://user@site.com 1 compose [
    ask find pp/(i) "Subject:"
]

Here are a few examples of how the "request" function can be used:

; Two different formats include passing either a string or a block.
; If you pass a string, the default buttons will be "yes", "no", and
; "cancel".  If you pass a block, you can specify the text on the
; buttons:

request "Could this be useful?"
request ["Just some information."]
request ["Here are 2 buttons with altered text:" "Probably" "Not Really"]
request ["3 buttons with altered text:" "Probably" "Not Really" "Dunno"]

; "Request" only returns 'true 'false or 'none.  For an example like the
; one below, the user response can be converted to strings using a switch
; structure:

answer: form request ["Complex example:" "choice 1" "choice 2" "choice 3"]
switch/default answer [
    "true"  [the-answer: "choice 1"]  
    "false" [the-answer: "choice 2"]
    "none"  [the-answer: "choice 3"]
] []
print the-answer

; The "/type" modifier changes the icon displayed:

request/type ["Here's a better icon for information display."] 'info
request/type ["Altered title and button text go in a block:" "Good"] 'info
request/ok/type "This example is the EXACT same thing as 'alert'." 'alert
request/ok/type "Here's alert with a different icon." 'info
request/ok/type "Here's another icon!" 'stop
halt

Here is a home made resizable requestor that I use when I don't want the "REBOL - " title bar to appear. It has a default timeout (set to 6 seconds in the example below), and can also be closed with a button click. This is particularly suitable for full screen commercial kiosk types of applications):

sz: 5
view layout [
    btn "Click here to see a requestor with a 6 second timeout" [
        view/new/options center-face information: layout [
            text font-size (8 * sz) "Here's a message!" rate :00:06 feel [
                engage: func [f a e] [
                    if a = 'time [unview/only information]
                ]
            ]
            box 1x1  ; spacer
            btn as-pair (12 * sz) (8 * sz) font-size (5 * sz) "Ok" [
                unview/only information
            ]
        ][no-title]
    ]
]

To edit the source of any mezzanine function, use the following format:

editor mold :request
editor mold :inform

I actually use the following script to check the source files of this tutorial, to make sure that none of the lines of code are wider than 79 characters:

REBOL [title: "Find Long Lines"]

doc: read/lines to-file request-file
the-text: {}
foreach line doc [
    if ((find/part line "   " 4)) [
        if ((length? line) > 78) [
            print line
            the-text: rejoin [the-text newline line]
        ]
    ]
]
editor the-text

Here's a duo of scripts that I use to sync my computer's clock to the time and date on my web server. The Windows API time setting function is based on Ladislav Mecir's Nist Clock Sync Script:

REBOL []

dif: 7:00  ; difference between web server and your local time zone
date: (to-date trim read http://site.com/time.cgi) + dif

lib: load/library %kernel32.dll

set-clock: make routine! [
    systemtime [struct! []]
    return:    [integer!]
] lib "SetSystemTime"

current: make struct! [
    wYear         [short]
    wMonth        [short]
    wDayOfWeek    [short]
    wDay          [short]
    wHour         [short]
    wMinute       [short]
    wSecond       [short]
    wMilliseconds [short]
] reduce [
    date/year
    date/month
    date/weekday
    date/day
    date/time/hour
    date/time/minute
    to-integer date/time/second
    0
]

either ((set-clock current) = 1) [
    ask rejoin ["Time has been set to:  " now "^/^/[Enter]... "]
] [
    ask "Error setting time.  Please check your Internet connection."
]

free lib

Here's the CGI script that the above code needs (to obtain the date and time from the web server). Put it at the URL which is read when the 'date word above is set:

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "time"]
print "content-type: text/html^/"
print now

Here's Ladislav's (better) version of the above Windows function. The script at http://www.fm.tul.cz/~ladislav/rebol/nistclock.r can set both Linux and Windows system clocks (the "set-system-time-lin" does the same thing in Linux):

the-date: to-date trim read http://site.com/time.cgi

set-system-time-win: func [
    {set system time in Windows; return True in case of success}
    [catch]
    date
    /local set-system-time
] [
    unless value? 'kernel32 [kernel32: load/library %kernel32.dll]
    set-system-time: make routine! [
        systemtime [struct! []]
        return: [int]
    ] kernel32 "SetSystemTime"
    date: date - date/zone
    date/zone: 0:0
    0 <> set-system-time make struct! [
        wYear [short]
        wMonth [short]
        wDayOfWeek [short]
        wDay [short]
        wHour [short]
        wMinute [short]
        wSecond [short]
        wMilliseconds [short]
    ] reduce [
        date/year
        date/month
        date/weekday
        date/day
        date/time/hour
        date/time/minute
        to integer! date/time/second
        0
    ]
]

set-system-time-win the-date

I use the following script to upload screen shots of my desktop directly to my web site (in the version I use, I put the text of the included script directly into my code):

REBOL []

do http://www.rebol.org/download-a-script.r?script-name=capture-screen.r

the-image: ftp://user:pass@site.com/path/current.png

; You can also save to your local hard drive if you want:
; the-image: %current.png

view center-face gui: layout [
    button 150 "Upload Screen Shot" [
        unview gui
        wait .2
        save/png the-image capture-screen
        view center-face gui
    ]
]

The following script demonstrates how to add and remove widgets from a GUI layout:

view gui: layout  [
    button1: button
    button2: button "remove" [
        remove find gui/pane button1
        show gui
    ]
    button3: button "add" [
        append gui/pane button1
        show gui
    ]
]

Here's a way to get a unique string identifier from the current time (useful for MCI buffer names and other situations when you need to generate absolutely unique identifier strings without any odd characters):

replace/all replace/all replace/all form now "/" "" ":" "x" "-" "q" "." ""

; precise version (w/ milliseconds):

replace/all replace/all replace/all replace/all form now/precise trim {
    /} "" ":" "x" "-" "q" "." ""

This script creates an image, saves it to the hard drive, and then opens it in mspaint.exe:

save/bmp %test.bmp to-image layout [box]
call/show join "mspaint.exe " to-local-file join what-dir %test.bmp

This script demonstrates how to use the AutoIT DLL to control the madplay.exe mp3 player:

REBOL []

if not exists? %AutoItDLL.dll [
    write/binary %AutoItDLL.dll
    read/binary http://musiclessonz.com/rebol_tutorial/AutoItDLL.dll
    write/binary %madplay.exe
    read/binary http://musiclessonz.com/rebol_tutorial/madplay.exe
]

lib: load/library %AutoItDLL.dll

move-mouse: make routine! [
    return: [integer!] x [integer!] y [integer!] z [integer!]
] lib "AUTOIT_MouseMove"

send-keys: make routine! [
    return: [integer!] keys [string!]
] lib "AUTOIT_Send"

winactivate: make routine! [
    return: [integer!] wintitle [string!] wintext [string!]
] lib "AUTOIT_WinActivate"

set-option: make routine! [
    return: [integer!] option [string!] param [integer!]
] lib "AUTOIT_SetTitleMatchMode" 

set-option "WinTitleMatchMode" 2
call/show {madplay.exe -v *.mp3}

view layout [
    across
    btn "forward" [
        winactivate "\reb" ""
        send-keys "f"
    ]
    btn "back"[
        winactivate "\reb" ""
        send-keys "b"
    ]
    btn "volume up" [
        winactivate "\reb" ""
        send-keys "+"
    ]
    btn "volume-down"[
        winactivate "\reb" ""
        send-keys "-"
    ]
    btn "pause" [
        winactivate "\reb" ""
        send-keys "p"
    ]
    btn "quit" [
        winactivate "\reb" ""
        send-keys "q"
        quit
    ]
]

Here's a quick and dirty way to print out help for all built in functions. Also includes a complete list of VID styles ("view layout" GUI widgets), VID layout words, and VID facets (standard properties available for all the VID styles). Give it a minute to run...

REBOL [title: "Quick Manual"]

print "This will take a minute..."  wait 2
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
x: read %help.txt
write %help.txt "VID STYLES (GUI WIDGETS):^/^/"
foreach i extract svv/vid-styles 2 [write/append %help.txt join i newline]
write/append %help.txt "^/^/LAYOUT WORDS:^/^/" 
foreach i svv/vid-words [write/append %help.txt join i newline]
b: copy [] 
foreach i svv/facet-words [
    if (not function? :i) [append b join to-string i "^/"]
]
write/append %help.txt rejoin [
    "^/^/STYLE FACETS (ATTRIBUTES):^/^/" b "^/^/SPECIAL STYLE FACETS:^/^/"
]
y: copy ""
foreach i (extract svv/vid-styles 2) [
    z: select svv/vid-styles i
    ; additional facets are held in a "words" block:
    if z/words [
        append y join i ": "
        foreach q z/words [if not (function? :q) [append y join q " "]]
        append y newline
    ]
]
write/append %help.txt rejoin [
    y "^/^/CORE FUNCTIONS:^/^/" at x 4
]
editor %help.txt

Here's an email program that demonstrates how to set all your email account settings:

m: system/schemes/default q: system/schemes/pop
view layout [ style f field
    u: f "username" p: f "password" s: f "smtp.address" o: f "pop.address"
    btn bold "Save Server Settings" [
        m/user: u/text m/pass: p/text m/host: s/text q/host: o/text
    ] tab
    e: f "user@website.com" j: f "Subject" t: area 
    btn bold "SEND" [
        send/subject to-email e/text t/text j/text alert "Sent"
    ] tab
    y: f "your.email@somesite.com"               
    btn bold "READ" [foreach i read to-url join "pop://" y/text [ask i]]
]

This example keeps a real time word count of text in an area widget. Changing the rate will reduce system resource usage, but also slow the response time (a rate of 3-4 updates per second should be suitable for most cases):

view layout [
    i: info rate 0 feel [
        engage: func [f a e] [
            if a = 'time [
                l: length? parse m/text none
                i/text: join "Wordcount: " l
                show i
            ]
        ]
    ]
    m: area 
]

Here are two versions of the VOIP program given earlier in the section about ports. These are likely the most compact VOIP programs you'll find anywhere. The first features port error handling, automatic localhost testing (just press [ENTER] to use localhost as the IP address), hands-free operation, and automatic minimum volume testing (squelch - data not sent unless a given volume is detected, to save bandwidth). It can be pasted directly into the REBOL console, or saved to a file and run. The second is barebones (user sees errors when the connection is broken, must be saved to a file and run, etc.) but it does work. The file sizes of these scripts are 693 bytes and 554 bytes!!:

REBOL[]do[write %w{REBOL[]if error? try[p: first wait open/binary/no-wait
tcp://:8][quit]wait 0 s: open sound:// forever[if error? try[m: find v:
copy wait p #""][quit]i: to-integer to-string copy/part v m while[i >
length? remove/part v next m][append v p]insert s load to-binary
decompress v]}launch %w lib: load/library %winmm.dll x: make routine![c[
string!]return:[logic!]]lib"mciExecute"if(i: ask"Connect to IP: ")=""[i:
"localhost"]if error? try[p: open/binary/no-wait rejoin[tcp:// i":8"]][
quit]x"open new type waveaudio alias b"forever[x"record b"wait 2 x
"save b r"x"delete b from 0"insert v: compress to-string read/binary
%r join l: length? v #""if l > 4000[insert p v]]]

REBOL[]write %w{REBOL[]if error? try[p: first wait open/binary/no-wait
tcp://:8][quit]wait 0 s: open sound:// forever[m: find v: copy wait p #""
i: to-integer to-string copy/part v m while[i > length? remove/part v next
m][append v p]insert s load v]}launch %w lib: load/library %winmm.dll x:
make routine![c[string!]return:[logic!]]lib"mciExecute"i: ask"IP: "p:
open/binary/no-wait rejoin[tcp:// i":8"]x"open new type waveaudio alias b"
forever[x"record b"wait 2 x"save b r"x"delete b from 0"insert v:
read/binary %r join length? v #""insert p v]

Here's an instant message example that allows users to upload their connection info (username, WAN IP, LAN IP, and network port), to a text file on an FTP server. Then others can simply click on their user name in a drop down box (choice button), to connect:

REBOL [title: "Instant Messenger"]

servers: ftp://username:password@yoursite.com/public_html/im.txt  ; EDIT
flash "Retrieving server list..."
if error? try [server-info: reverse read/lines servers] [
    alert "Internet connection not available."
    server-info: copy []
]
unview
name-list: copy []
foreach server server-info [append name-list first to-block server]
insert head name-list "Connect to a Server:"

view center-face layout [
    across
    choice 280 data name-list [
        mode: request [ {
            SELECT MODE: By default, this program is able to connect to
            a server running on any other computer in your Local Area
            Network.  Choosing "LAN" mode connects you to a server's local
            IP address.  If you select "Internet" Mode, you will connect
            to the server's WAN IP address (typically the address of
            the user's _router_).  In order for Internet mode to work
            correctly, the selected port number chosen by the server user
            must be exposed on the Internet, or be forwarded from their
            router to the IP address of the computer running the server
            program.
        } "LAN" "Internet"]
        foreach server server-info [
            server-block: parse server " "
            if ((form first server-block) = form value) [
                b/text: server-block/1  show b
                either mode = false [
                    remote-ip: server-block/2
                ] [
                    remote-ip: server-block/3
                ]
                j/text: server-block/4
                show j
                p: open/lines rejoin [tcp:// remote-ip j/text]
                z: 1
                focus g
                break
            ]
        ]
    ]
    text "OR run as server:"
    b: field 106 "Username"
    text "Port: "
    j: field 55 ":8080"
    q: button 84 "Start Server" [
        parse read http://guitarz.org/ip.cgi [
            thru <title> copy p to </title>
        ]
        parse p [thru "Your IP Address is: " copy wan-ip to end]
        write/append servers rejoin [
            b/text " "                           ; username
            wan-ip " "                           ; wan ip
            read join dns:// read dns:// "  "    ; local ip
            j/text "^/"                          ; port
        ]
        alert {
            Server is running.  Connections from clients running on
            your Local Area Network should work without any problems.
            If you want to accept connections from the Internet, and
            you are connected by a router, then the port number you've
            selected must be forwarded from your router to the IP
            address of this computer (see portforward.com for more
            information about forwarding ports).
        }
        focus g 
        p: first wait open/lines (join tcp:// j/text)
        z: 1
    ]
    return
    r: area 700x400 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 400 "Type message here, then press [ENTER]" [
        r/text: rejoin ["<--  " value "^/" r/text]
        show r
        insert p value
        focus face
    ]
    tabs 618
    tab
    button "Save Chat Text" [editor r/text]
    return
]

The following function converts number values to their spoken English equivalent (i.e., 23482194 = "Twenty Three million, Four Hundred Eighty Two thousand, One Hundred Ninety Four"). This code was created for a check writing application, but is perhaps useful elsewhere. The algorithm was partially derived from the article at http://www.blackwasp.co.uk/NumberToWords.aspx (C# code):

REBOL [title: "Number Verbalize"] 

verbalize: func [a-number] [
    if error? try [a-number: to-decimal a-number] [
        return "** Error **  Input must be a decimal value"
    ]
    if a-number = 0 [return "Zero"]
    the-original-number: round/down a-number
    pennies: a-number - the-original-number
    the-number: the-original-number
    if a-number < 1 [
        return join to-integer ((round/to pennies .01) * 100) "/100"
    ] 
    small-numbers: [
        "One" "Two" "Three" "Four" "Five" "Six" "Seven" "Eight"
        "Nine" "Ten" "Eleven" "Twelve" "Thirteen" "Fourteen" "Fifteen"
        "Sixteen" "Seventeen" "Eighteen" "Nineteen"
    ]
    tens-block: [
        { } "Twenty" "Thirty" "Forty" "Fifty" "Sixty" "Seventy" "Eighty"
        "Ninety"
    ]
    big-numbers-block: ["Thousand" "Million" "Billion"]    
    digit-groups: copy []
    for i 0 4 1 [
        append digit-groups (round/floor (mod the-number 1000))
        the-number: the-number / 1000
    ]    
    spoken: copy ""
    for i 5 1 -1 [
        flag: false
        hundreds: (pick digit-groups i) / 100
        tens-units: mod (pick digit-groups i) 100
        if hundreds <> 0 [
            if none <> hundreds-portion: (pick small-numbers hundreds) [
                append spoken join hundreds-portion " Hundred "
            ]
            flag: true
        ]
        tens: tens-units / 10
        units: mod tens-units 10
        if tens >= 2 [
            append spoken (pick tens-block tens)
            if units <> 0 [
                if none <> last-portion: (pick small-numbers units) [
                    append spoken rejoin [" " last-portion " "]
                ]
                flag: true
            ]
        ]
        if tens-units <> 0 [
            if none <> tens-portion: (pick small-numbers tens-units) [
                append spoken join tens-portion " "
            ]
            flag: true
        ]
        if flag = true [
            commas: copy {}    
            case [
                ((i = 4) and (the-original-number > 999999999)) [
                    commas: {billion, }
                ]
                ((i = 3) and (the-original-number > 999999)) [
                    commas: {million, }
                ]
                ((i = 2) and (the-original-number > 999)) [
                    commas: {thousand, }
                ]
            ]
            append spoken commas
        ]
    ]
    append spoken rejoin [
        "and " to-integer ((round/to pennies .01) * 100) "/100"
     ]
    return spoken
]

; HERE'S AN EXAMPLE OF HOW TO USE IT:

print verbalize ask "Enter a number to verbalize: "
halt

Here's a fun program that lets you record your voice or other sounds to be played as alarms for any number of multiple events. Save and Load event lists. All alarm sounds repeat until stopped. Record yourself saying 'wake up you lazy bum' or 'hey dude, get up and walk the dog', then set alarms to play those voice messages on any given day/time. If you set the alarm as a date/time, the alarm will go off only once, on that date. If you set the alarm as a time, the alarm will go off every day at that time. The .wav recording code is MS Windows only, but the program can play any wave file that is usable in REBOL:

REBOL [title: "Voice Alarms"]

lib: load/library %winmm.dll
mci: make routine! [c [string!] return: [logic!]] lib "mciExecute"

write %play-alarm.r {
    REBOL []
    wait 0
    the-sound: load %tmp.wav
    evnt: load %event.tmp
    if (evnt = []) [evnt: "Test"]
    forever [
        if error? try [
             insert s: open sound:// the-sound wait s close s
        ] [
             alert "Error playing sound!"
        ]
        delay: :00:07
        s: request/timeout [
            join uppercase evnt " alarm - repeats until you click 'stop':"
            "Continue"
            "STOP"
        ] delay
        if s = false [break]
    ]
}

current: rejoin [form now/date newline form now/time]

view center-face layout [
    c: box black 400x200 font-size 50 current rate :00:01 feel [
        engage: func [f a e] [
            if a = 'time [
                c/text: rejoin [form now/date newline form now/time]
                show c
                if error? try [
                    foreach evnt (to-block events/text) [
                        if any [
                             evnt/1 = form rejoin [
                                 now/date {/} now/time
                             ]
                             evnt/1 = form now/time  
                        ] [
                            if error? try [
                                 save %event.tmp form evnt/3
                                 write/binary %tmp.wav 
                                 read/binary to-file evnt/2
                                 launch %play-alarm.r
                            ] [
                                 alert "Error playing sound!"
                            ]
                            ; request/timeout [(form evnt/3) "Ok"] :00:05
                        ]
                    ]
                ] []  ; do nothing if user is manually editing events
            ] 
        ] 
    ]
    h3 "Alarm Events (these CAN be edited manually):"
    events: area  ; {[8:00:00am %alarm1.wav "Test Alarm - DELETE ME"]}
    across
    btn "Record Alarm Sound" [
        mci "open new type waveaudio alias wav"
        mci "record wav"
        request ["*** NOW RECORDING *** Click 'stop' to end:" "STOP"]
        mci "stop wav"
        if error? try [x: first request-file/file/save %alarm1.wav] [
            mci "close wav"
            return
        ]
        mci rejoin ["save wav " to-local-file x]
        mci "close wav"
        request [rejoin ["Here's how " form x " sounds..."] "Listen"]
        if error? try [
            save %event.tmp "test"
            write/binary %tmp.wav 
            read/binary to-file x
            launch %play-alarm.r
        ] [
            alert "Error playing sound!"
        ]
    ]
    btn "Add Event" [
        event-name: request-text/title/default "Event Title:" "Event 1"
        the-time: request-text/title/default "Enter a date/time:" rejoin [
            now/date {/} now/time
        ]
        if error? try [set-time: to-date the-time] [
            if error? try [set-time: to-time the-time] [
                alert "Not a valid time!"
                break
            ]
        ]
        my-sound: request-file/title/file ".WAV file:""" %alarm1.wav
        if my-sound = none [break]
        event-block: copy []
        append event-block form the-time
        append event-block my-sound
        append event-block event-name
        either events/text = "" [spacer: ""][spacer: newline]
        events/text: rejoin [events/text spacer (mold event-block)]
        show events
    ]
    btn "Save Events" [
        write to-file request-file/file/save %alarm_events.txt events/text
    ]
    btn "Load Events" [
        if error? try [
            events/text: read to-file request-file/file %alarm_events.txt
        ] [return]
        show events
    ]
]

Here is a nice generic CGI example which demonstrates how to enter and decode both Get and Post data, slightly revised from the earlier examples in this text:

#!./rebol276 -cs
REBOL[]
print "content-type: text/html^/"
either system/options/cgi/request-method = "POST" [
    data: copy "" buffer: copy ""
    while [positive? read-io system/ports/input buffer 16380][
        append data buffer
        clear buffer
    ]
] [
    data: system/options/cgi/query-string
]
cgi: construct decode-cgi data
if (length? first cgi) < 2 [
    print {
        <FORM METHOD="post" ACTION="./test.cgi">
        <CENTER>
        <INPUT TYPE=hidden NAME=hiddenvalue VALUE="foo">
        <INPUT TYPE=text size=50 name=text><BR><BR>
        <TEXTAREA cols=75 name=message rows=5></textarea><br><br>
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </CENTER>
        <FORM>
    }
    quit
]
print rejoin [
    cgi/hiddenvalue "<br><br>"
    cgi/text "<br><br>"
    "<pre>" cgi/message "</pre>"
]

Here is another version of Andreas Bolka's decode-multipart-form-data function (covered in the section of this tutorial about CGI programming):

decode-multipart-form-data: func [
    p-content-type
    p-post-data
    /local list ct pd bd delim-beg delim-end mime-part ] [
    list: copy []
    if not found? find p-content-type "multipart/form-data" [ return list ]
ct: copy p-content-type
pd: copy p-post-data
bd: join "--" copy find/tail ct "boundary="
delim-beg: join crlf crlf
delim-end: rejoin [ crlf bd ]
mime-part: [
    ( ct-dispo: content: none ct-type: "text/plain" )
    thru bd
    thru "content-disposition: " copy ct-dispo to crlf
    opt [ thru "content-type: " copy ct-type to crlf ]
    thru delim-beg copy content to delim-end
    ( handle-mime-part ct-dispo ct-type content )
]
handle-mime-part: func [
    p-ct-dispo p-ct-type p-content /local fieldname 
] [
p-ct-dispo: parse p-ct-dispo [describe ;=" here]
fieldname: select p-ct-dispo "name"
append list to-set-word fieldname
either found? find p-ct-type "text/plain" [append list content][
    append list make object! [
        filename: select p-ct-dispo "filename"
        type: copy p-ct-type
        content: either none? p-content [ none ] [ copy p-content ]
    ]
]
]
use [ ct-dispo ct-type content ] [
parse/all pd [ some mime-part ]
]
return list
]
]

This is a set of scripts by Andrew Grossman and Luis Rodriguez Jurado which also work with form-multipart data (just an example - NOT necessary for production use if you have Andreas's function):

#!c:/rebol.exe -cs

REBOL [
TITLE: "form-upload"
FILE: %form-upload.r
DATE: 29-April-2000
]
print "Content-Type: text/html^/^/"
print {
<form METHOD=POST ACTION="post.r"
 enctype="multipart/form-data">
 Enter a description of the file:: <input TYPE=text
 SIZE=50 NAME=text><br>
 Select the file: <input TYPE=file SIZE=15
 NAME=myUpload><br><br><br>
 <input TYPE=submit VALUE="post.r">
</FORM></INPUT>
}

#!c:/rebol.exe -cs
REBOL [
    Title:      "multipart POST"
    Date:       15-Sep-1999
    Version:    1.2
    File:       %POST.r
    Author:     {Andrew Grossman (modified by: Luis Rodriguez J -
.29-April-2000)}
    Email:      [grossdog--dartmouth--edu]
    Owner:      "REBOL Technologies"
Purpose:    {
    To decode multipart encoded POST requests, including file uploads.
}
Usage:      {
    Call this file in your CGI scriptand do decode-multi with a
    file argument of the directory where uploaded files will go and
    a logic argument to set whether files will be given the field they
    were uploaded as a name.  Files are saved and variables are
    decoded and set.
}
Notes:      {
    Fixed problem recognizing EOF.
    Functionality is now rock solid.  Function
    calls won't change, so this is certainly useable.
    See the comment in the decode-multi function if you need mime
    types.  Tested with MSIE and Netscape for Mac.
}
category: ['web 'cgi 'utility]
]
decode-multi: func [
file-dir      [file!]  {Directory in which to put uploaded files}
save-as-field [logic!] {save files as field name or uploaded filename}
/local str boundary done name file done2 content
][
if equal? system/options/cgi/request-method "POST" [
    either not parse system/options/cgi/content-type
        ["multipart/form-data" skip thru {boundary=} skip some {-}
            copy boundary to end]
str:   make string! input do decode-cgi str][
str:    make string! input
done:   false
while [not done] [
    name:   make string! ""
    str:    make string! input
    either equal? "" str [done: true] [
        either parse/all str [skip thru {name="} copy name to {"}
            skip thru {filename="} copy file to {"} skip to end] [
            str: make string! input
            if not equal? str "" [str: make string! input]
            comment {if you need mime put "parse/all str [
              "Content-Type:" skip copy mime to end
            ]" into the preceding if block.}
            done2:      false
            content:    make string! ""
            while [not done2] [
content-length: to-integer system/options/cgi/content-length
str: make string! content-length
read-io system/ports/input str content-length
                either d: find/reverse tail str boundary [
                   e: find/reverse tail copy/part str (index? d)
                       {^/}
                       content: copy/part str (index? e) - 2
                 done2: true]
                     [
                     append content str
                 ]
            ]
            if not none? file
either save-as-field [name: dehex name write/binary
     file-dir/:name content] [
     file: dehex file write/binary file-dir/:file
     content
 ]
]
][
parse str [skip thru {name="} copy name to {"}]
str: make string! input str: dehex make string! input
set to-word name str str: make string! input
]
]
]
]
]
]
decode-multi %. true

This is a slightly edited version of the 3D Maze program (raycasting engine) by Olivier Auverlot:

REBOL [title: "3D Maze - Ray Casting Example"] 

px: 9 * 1024  py: 11 * 1024 stride: 2 heading: 0 turn: 5
laby: [ 
    [ 8 7 8 7 8 7 8 7 8 7 8 7 ] 
    [ 7 0 0 0 0 0 0 0 13 0 0 8 ] 
    [ 8 0 0 0 12 0 0 0 14 0 9 7 ] 
    [ 7 0 0 0 12 0 4 0 13 0 0 8 ] 
    [ 8 0 4 11 11 0 3 0 0 0 0 7 ] 
    [ 7 0 3 0 12 3 4 3 4 3 0 8 ] 
    [ 8 0 4 0 0 0 3 0 3 0 0 7 ] 
    [ 7 0 3 0 0 0 4 0 4 0 9 8 ] 
    [ 8 0 4 0 0 0 0 0 0 0 0 7 ] 
    [ 7 0 5 6 5 6 0 0 0 0 0 8 ] 
    [ 8 0 0 0 0 0 0 0 0 0 0 7 ] 
    [ 8 7 8 7 8 7 8 7 8 7 8 7 ] 
] 
ctable: [] 
for a 0 (718 + 180) 1 [ 
    append ctable to-integer (((cosine a ) * 1024) / 20) 
] 
palette: [ 
    0.0.128 0.128.0 0.128.128 
    0.0.128 128.0.128 128.128.0 192.192.192 
    128.128.128 0.0.255 0.255.0 255.255.0 
    0.0.255 255.0.255 0.255.255 255.255.255 
] 
get-angle: func [ v ] [ pick ctable (v + 1) ]
retrace: does [ 
    clear display/effect/draw 
    xy1: xy2: 0x0 
    angle: remainder (heading - 66) 720 
    if angle < 0 [ angle: angle + 720 ] 
    for a angle (angle + 89) 1 [ 
        xx: px 
        yy: py 
        stepx: get-angle a + 90
        stepy: get-angle a 
        l: 0 
        until [ 
            xx: xx - stepx 
            yy: yy - stepy 
            l: l + 1 
            column: make integer! (xx / 1024) 
            line: make integer! (yy / 1024) 
            laby/:line/:column <> 0
        ] 
        h: make integer! (1800 / l) 
        xy1/y: 200 - h 
        xy2/y: 200 + h 
        xy2/x: xy1/x + 6 
        color: pick palette laby/:line/:column 
        append display/effect/draw reduce [ 
            'pen color 
            'fill-pen color 
            'box xy1 xy2 
        ] 
        xy1/x: xy2/x + 2  ; set to 1 for smooth walls 
    ] 
] 
player-move: function [ /backwards ] [ mul ] [ 
    either backwards [ mul: -1 ] [ mul: 1 ] 
    newpx: px - ((get-angle (heading + 90)) * stride * mul) 
    newpy: py - ((get-angle heading) * stride * mul) 
    c: make integer! (newpx / 1024) 
    l: make integer! (newpy / 1024) 
    if laby/:l/:c = 0 [ 
        px: newpx 
        py: newpy 
        refresh-display 
    ] 
] 
evt-key: function [ f event ] [] [ 
    if (event/type = 'key) [ 
        switch event/key [ 
            up [ player-move ] 
            down [ player-move/backwards ] 
            left [ 
                heading: remainder (heading + (720 - turn)) 720 
                refresh-display 
            ] 
            right [ 
                heading: remainder (heading + turn) 720
                refresh-display 
            ] 
        ] 
    ] 
    event 
] 
insert-event-func :evt-key 
refresh-display: does [ 
    retrace 
    show display 
] 
screen: layout [ 
    display: box 720x400 effect [ 
        gradient 0x1 0.0.0 128.128.128 
        draw [] 
    ] 
    edge [ 
        size: 1x1 
        color: 255.255.255 
    ] 
] 
refresh-display 
view screen

Here are a couple tiny utility scripts that I found useful:

; to replace a specific string inside special characters:

code: "text1 <% replace this %> text3"
replace code "<% replace this %>" "<% text2 %>"
print code

; to replace everything between special characters: 

code: "text1 <% replace this %> <% replace this %> text3"
parse/all code [
    any [thru "<%" copy new to "%>" (replace code new " text2 ")] to end
]
print code

This script demonstrates how to insert a string into a file at a found position:

REBOL []

file: %mp3.html
a-string: {<BODY bgcolor="#C8C8C8">}

; first way:

write file read http://re-bol.com/examples/mp3.html
content: read file
insert (skip find content a-string length? a-string) trim {
    <center><h1>MP3 Example!</h1></center>}
write file content
editor file

; second way:

write file read http://re-bol.com/examples/mp3.html
content: read file
begin: (index? find content a-string) + (length? a-string)
altered: rejoin [
    (copy/part content begin)
    {<center><h1>MP3 Example!</h1></center>}
    (at content begin)
]
write file altered
editor file

This code determines the operating system you're running:

switch system/version/4 [
    2 [print "OSX"]
    3 [print "Windows"]
    4 [print "Linux"]
    7 [print "FreeBSD"]
    8 [print "NetBSD"]
    9 [print "OpenBSD"]
    10 [print "Solaris"]
] [alert "Can't be dertermined"]

Here's a CGI program I keep on my web server to delete masses of email which contain any given "spam" text:

#! /home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Remove Emails"</TITLE></HEAD><BODY>]

spam: [
    {Failure} {Undeliverable} {failed} {Returned Mail} {not be delivered}
    {mail status notification} {Mail Delivery Subsystem} {(Delay)}
]

print "logging in..."
mail: open pop://user:pass@site.com
print "logged in"

while [not tail? mail] [
    either any [
        (find first mail spam/1) (find first mail spam/2)
        (find first mail spam/3) (find first mail spam/4)
        (find first mail spam/5) (find first mail spam/6)
        (find first mail spam/7) (find first mail spam/8)
    ][
        remove mail
        print "removed"
    ][
        mail: next mail        
    ] 
    print length? mail 
]
close mail
print [</BODY></HTML>]
halt

The following utility script takes an input string and returns and HTML string with all the web URLs appropriately wrapped as links:

bb:  "some text http://guitarz.org http://yahoo.com"
bb_temp: copy bb
append bb_temp " " ; in case the URL doesn't have a trailing space
append bb " "
parse bb [any [thru "http://" copy link to " " (
    replace bb_temp (rejoin [{http://} link]) (rejoin [
    {<a href="} {http://} link {" target=_blank>http://} 
    link {</a>}]))] to end
]
bb: copy bb_temp
print bb

I use the following utility CGI script to copy entire directories of files from one web server to another:

#!/home/path/public_html/rebol/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"wgetter"</TITLE></HEAD><BODY>]
foreach file (read ftp://user:pass@site.com/public_html/path/) [
    print file
    print <BR>
    write/binary (to-file file) 
        (read/binary (to-url (rejoin [http://site.com/path/ file])))
]
print [</BODY></HTML>]

I use this next script to make sure that there are no files chmod 777 on my webservers. Built in is a routine that writes the name of every folder and every file on my server, to a text file. I run this one in the CGI console, with a "do chmod777to555.r":

REBOL [title: "chmod777to555"]

start-dir: what-dir
all-files: to-file join start-dir %find777all.txt

write all-files ""

recurse: func [current-folder] [
    out-data: copy ""
    write/append all-files rejoin["CURRENT_DIRECTORY:  " what-dir newline]
    call/output {ls -al} out-data
    write/append all-files join out-data newline
    foreach item (read current-folder) [ 
        if dir? item [
            change-dir item 
            recurse %.\
            change-dir %..\
        ] 
    ]
]
recurse %.\

file-list: to-file join start-dir %found777.txt
write file-list ""
current-directory: ""
foreach line (read/lines all-files) [
    if find line "CURRENT_DIRECTORY:  " [
        current-directory: line
    ]
    if find line "rwxrwxrwx" [
        write/append file-list rejoin [
            (find/match current-directory "CURRENT_DIRECTORY:  ")
            (last parse/all line " ")
        ]
        write/append file-list newline
    ]
]

foreach file (read/lines file-list) [
    call rejoin [{chmod 755 } (to-local-file file)]
]

I've used variations of the following script to rename all the files with a given extension in a folder, to a different extension. The script copies all the files to the same name, without any extension at all:

foreach file read %. [
    if (suffix? file) = %.src [
        write (to-file first parse file ".")(read to-file file)
    ]
]

Here's how to move one item in a series to a different position:

x: ["one" "two" "three" "four" "five" "six"]
move/to (find x "five") 2
print x

I use the following script to keep my collection of Haxe language libraries up to date:

REBOL []

write %haxelibs.txt read http://lib.haxe.org/files/

the-list: read/lines %haxelibs.txt
clean: copy []

foreach line the-list [
    x: (parse/all form (find line ".zip") ">")
    if (length? x) > 2 [
        y: parse form second x "<"
        append clean first y
    ]
]

errors: copy []
make-dir %haxelibs
change-dir %haxelibs
save %list.txt clean  ; comment this if you need to edit list.txt

downloaded: read %.
if exists? %previously_downloaded.txt [
    append downloaded load %previously_downloaded.txt
]
save %previously_downloaded.txt unique downloaded
; editor downloaded

foreach file clean [
    if not (find downloaded (to-file file)) [
        either error? try [size? (join http://lib.haxe.org/files/ file)] [
            print join "ERROR:  " file
            append errors file
        ] [
            print rejoin [
                {Downloading:  } file {  (} 
                size? (join http://lib.haxe.org/files/ file) { kb)}
            ]
            if error? try [
                write/binary 
                    (to-file file)
                    (read/binary (join http://lib.haxe.org/files/ file))
            ] [
                print join "ERROR:  " file
                append errors file
            ]
        ]
    ]
]

save %haxe_lib_download_errors.txt errors
halt
foreach file clean [if find downloaded (to-file file) [print file]]

I use the following line to view/edit the code of script which has been run directly from a zip file (compressed folder) in Windows:

editor to-file request-file system/script/path

Here's how to refer to widgets in the main window pane of a GUI window:

foreach item system/view/screen-face/pane/1/pane [
    remove find system/view/screen-face/pane/1/pane item
    ; this removes all widgets
]

Here is a Windows API function to use MCI functions:

lib: load/library %winmm.dll
mciSendString: make routine! [
    command [string!]
    rStr [string!]
    cchReturn [integer!] 
    hwndCallBack [integer!] 
    return: [integer!]
] lib "mciSendStringA"

The following example demonstrates some techniques that can be used to position widgets relative to one another when a GUI is resized:

REBOL []

svv/vid-face/color: white
view/new/options gui: layout [
    across
    t1: text "50x50"
    t2: text "- 50x25"
    t3: text "- 25x50"
] [resize]

insert-event-func [
    either event/type = 'resize [
        fs: t1/parent-face/size
        t1/offset: fs / 2x2
        t2/offset: t1/offset - 50x25
        t3/offset: t1/offset - 25x50
        show gui  none
    ] [event]
]

ss: system/view/screen-face/size
gui/size: (ss - 200x200)
gui/offset: (ss / 2x2) - (gui/size / 2x2)
show gui  do-events

The following scripts demonstrate several different ways to run the code from the action block of another widget (i.e., to simulate mouse clicks or other actions on any given face). To understand more, run "source do-face" and "source do-face-alt" in the REBOL console to see how the "do-face" and "do-alt-face" functions work:

view layout [
    button1: btn "Button 1" [alert "Button 1 action block has been run."]
    btn "Button 2" [do-face button1 1]
]

view layout [
    b1: btn "B1" [alert "B1 left click"] [alert "B1 right click"]
    btn "B2" [do-face b1 1] [do-face-alt b1 1]
]

view layout [
    button1: btn "Button 1" [alert "Button 1 action block has been run."]
    btn "Button 2" [button1/action button1 none]
    ; "button1 none" in the line above releases the down state of the btn
]

The following script from http://www.pat665.free.fr/gtk/rebol-view.html demonstrates another way to do the same thing:

view layout [
    b: button "Test" [print "Test pressed"]
    button "In" [b/state: true show b]
    button "Out" [b/state: false show b]
    a: button "Action" [
        b/feel/engage :b 'down none
        b/feel/engage :b 'up none
        a/state: false show a ; Not sure why this line is needed...
    ]
]

Here's a 92 character version of the classic "FizzBuzz" program:

repeat i 100[j:""if i // 3 = 0[j:"fizz"]if i // 5 = 0
    [j: join j"buzz"]if j =""[j: i]print j]

The following example demonstrates how to use the Captcha library from Softinov:

REBOL []

write/binary %Caliban.caf read/binary http://re-bol.com/Caliban.caf
do http://re-bol.com/captcha.r

captcha/set-fonts-path %./
captcha/level: 4
write/binary %captcha.png captcha/generate
write %captcha.txt captcha/text

view center-face layout [
    image (load %captcha.png)
    text "Enter the captcha text:"
    f1: field [
        either f1/text = (read %captcha.txt) [
            alert "Correct"
        ] [
            alert "Incorrect"
        ]
    ] 
]

Here's a way to get separate characters from random words (this was originally intended to be used as part of a captcha routine):

x: copy []
wrds: (first system/words)
foreach ch mold pick wrds (random length? wrds) [append x ch]
print x

Here's the code for the forum CGI application at http://rebolforum.com:

editor decompress #{
789CDD5B7B73DB3892FF9F9F02A7A9CCDAB52B0B00DF1E4929802027A9CB63CE
F1EED6954B374749B4C58D246A483A1E9F4BDFFD1A7C8212FDCADDD46D1D9348
2280EE061ADD3F7437991FFE65B44A36D16817E6ABD1EE76BE8E17BFAEF2CD7A
9446F3A4FA44C345A65DF8FCF3077435D37669BCCDD1C322D9E6D1361FE6F7BB
E81CE5D1EFF948D2FDC768AF69D95D9C2F5628BBCFF268334A76799C6CB3D1E2
26067EBFDD46593EDC44F92A59A22B0DC135F8E5F397CB4175232F18395C8679
788E16C9EE1E0D0665D3FCF6FA3A4A9BC666F8DD2A5E47E86A9764711E7F8BDE
A2340A97C338A927B04BD23C1BC5DBDD6DAEF041C4D21D3C53C4CA2BDCEDA2ED
B299813A7EB18EC2546968E8665AFB39F8D9974B6957D0A30450417A3FCC7250
E4CD4C9B69D9ED7C13E779B43C47CB68912CA3218C6AA6A069F1356A868C289A
A0419A65B5BEEED2388F46D5B4DFCCE767CB79AD9BF93C3B47EB24ACDB8BC634
FA16A559243B8BFB723B612E1BF4307EFBFB668D643FCC7532206778F076DA2C
730C52DB4E0A9D4ADF62156EB7D17A5A37B43D799CAFA369693E4192DE6EC6A3
B2A91DB28CB2451A171A9A5E440B302BA48C4797C92E5E64E3913AAC255EC7DB
AFD3559EEFCE47A5B95E4BA2B34502828ABE62E8BEF85C24B7DBFC1C91E206C6
45219829E86F23D5A15802AC74584CB23236396444FABAD368B70E17B001EB75
DB8C1E7EDCC3BF70B3FB69FF0AA28124FAED36C95F45F5A7421418FFABA8C692
EAE67592A692667D48A3EC8AA4FC47126FD1D5E09704CC7E89E6F7E76850AA4F
871FF56F63D6B050ECAF26EE38E4C358524C3B6D954DED3B0B3AB4AA23CBDA1F
4E5712F5DAD433760550B28C7E3F03077D7B3D29E69F4B0B2DF6BBF8B5BDDDCC
A374B2EF303C395947DB9B7CF556DADA29FA332268581AE4A99C486BAACD0C46
C5CAF70738A3D871F12539351D001565E314118CAEE660E15F557852BDBD75A1
51EDBD4A13E84AF59CDF6EE31CA0AA46FFF1BBCB8F1FA6E3773E13D3F1E5FBCB
0F7ED7C3CBA6F1A818A08DF967F1EF687EE325EB249DFCE060F9673AF6FC4F97
FE05745F32FEC147F3245D82D6305A44EBF52FE17209F038A1E5ED975DB8286E
D12A8A6F56F98460FC06DDC5CB7C3571CC3792C7054C443442E04CC823F48DAD
E39BED043605D4A85D27492ECF0FD0F6254C7B244946856CF8AE26331EC9B9CA
99CB05EE7BC0779B6CA3CA46D370BB0473C822B0F46D720716B8894A35875976
07CBA9E0E36A5641F51200F93A4EB3BC3E16E4A0AC8346F07793AC9708CCE86B
41814E4A31A8361FD9788AAE2AC8AF4501A1BAC547BEF43096B81AA5D3F1359C
DC288BFF2B9A58DD4D931DD3F13C2DFFADAAEF5145B75758C1D9956C6FA60058
8DF887C1785435D72C548AE0F3C547549EFA930180553E40CCBB7CFFF9D36470
D6FAD360BA3F74FEE2D49641C664B08A97CB683B40DB700377B5E4C52A5A7C1D
A06FE1FAF6A8F590DD25F041F92A42126561D316E10E0295B0085F50384FBE01
F8215566D151E86A606255F2A067952F9D7133D98EFA9E64549A60CDA8BEABD8
54B71D7D8FA4C2AB16D52E4A1FE8B8F4717871A0DAD2883AC30C349EA2138923
C521D17658A78ACDD56051D9C5FBED2249D3689123AFD2FB25A8B7319BFD019D
32D5C27BC23847E7189F63FD48C047FF92A1779797BF0CFD7FFBEBFBBF4D0617
7E70E17F793740DE6770EB4F979301FE09FDF5E2C3E411632B7451EAAA5723CB
E46E2B232919594669AD92164D257C47998AA9B5B785689546D71D2B875343C6
DC83E93BF81C8FC2D6C7A43D352C4E3E838317D65A7A2944DC5902471DA0819C
D000BAE20C2CE826922B3B6D1C7797462A7297939481E5067A2084FBE141B31D
D7F34CD330B8C305D731C1DC0FB86963CFA106353D1B73C3F34D4A2CDBB3B8E5
06363798E962E21B3AF639D574215CE670939AA64D98670BD7B5023FB00D1650
AA1B304470CF66AE4309B65CDF72E12EB01DC1CCC0746CDBD6DC80F816367497
52E6790E66C2D30362FA2EE79663E9A6C308C54270C3720915D863D4631ED67D
D3F5A8CB48A051C3B6B8C76D6AEADC84B1DC0EA8CE0D8AA9EF7AC21382048252
411D413DCBC396635A3E4C04C36A4DEA6053735CD333410F86671261E896E7FB
0EC8310D18E372EC088F3338A6E47CB8E718B05A417DE25257300F4E1F4FB37D
8B0B6252DD09B00123036E09C7097487EBC27602C300A599962EA72E88A3636C
B1007B40C404B35DDBD70CAE33937A9833A23BCCA22210B61B50867D43042E35
5D862DD3D4DDC0C0B60EBB403DA93C07FB0EE1B01B8616C0C418F1409504BE6C
E0AF73DFC6C2E62EB1A9073B8105C324D00323C0D8258665F8C4A001F508AC07
3B9A2108261665016186C90933197629A381CD84F075D07660116E70D3D003D8
031B54C40C18E073EE9906158EC60C4240C1D8A33E28D885DD090CE278AEA9C3
3C4183C27439812DB01D4E99F081C41356609B3670E0DC111AE805A609E6A363
2730196CBD0874CFD25DDDB2856105BA6E48E5BA8439C4321C617253060F14F4
E7F8DC7035D3A2B6693082417E4040453658AEC1E524B8EF831A8430B16939C2
C20E757C8F32E65A5610709F325BB7740D07B009018CB261FE4E406DDBE39619
78A660CC12B6C185CE490093722CC7A6B0D7BA1E6013BBBAAD7BA04D437340A0
E3F89617783AE73687BD05B37785412CE608B06CEE82BE39062762A6EE83DD33
30083B303DA1BBA685355DB7C18861B35C37E0A004D3229EEB83EE40979E096C
7D7013D3360262633F000B0F847456A2BB9C52D3F7C094C119BD40272E70C63A
9004BA65BAB0191E7818B3032C2C62C8997918BC2470B1E11886094A80753A8E
A75102162B0571CCA8CE840D7B0F2AE1BE237CD83027E000109880DDF2C095AB
874B53E1050E1C093BFBD79E34E12EEE82E9C3F890CF317271D3C4BAC0DCA312
B904288372C7C7BECE8809DB4F890DFAF61D0A33663AF38C002C1C50C4A26EE0
6A604E46C0446008A10BB00A58AFE33860AE86B4486CFB81AF836172E2079601
6EAE4B1FB288F422E1013C689C82AB510A80E8E9CC10D8268480F3188C815026
2C0A9A665C086C82B902B812CAC0B38863FB064029F33462B880A826182CF483
C963C3378943319009DF252EE61496E510C281849B60AF2085C1C60026807B62
001E00D7800BEC0B265C034010E0D567DC056B12804982007B6273AC53F022DB
D00D50144C91EAD4B6B007D8E9041E0C07B77001D4A40A7460033A324D98A201
20EA73DD07DBD10D8F80862D30770070F07ECB0528631A802D782230F6194CC4
B56CC77405201BF84BE003726058A3473D600A3AA4DF692DDDCA46982E56F1B7
A82A6F3C52F538AA782CE30C52D9FB32743E47AEDB6B84CBE536BA53E29C1335
D08101835394A46AABD5DBEA54AD47015037AD53A2A1CD6E1D419A52B0C8645D
E52886EE907A6B9914C888E08A33EF5F67687E0BA9E71641A07F9FDCA6689E26
775994A23C81A0E41E853761BC3DFBCFAEF43A4A78515CA24EE099F04C89A0E4
E726FC1A0D97718ADE9C8D204CC993F47E547464E1B7E8383B51461599D4BD2C
B50D7E1D14371BC84B56CDDD32BC2F7EB771925AAE80346AD3E46268703E9063
4FD1008CA12C8ACD9ACA17845CE97D3743AB0CAAE851835A847E4245665F2EA1
EE186E009520FEEA564C548378A8CAA67BF8857CC4D16724EF5EC5A6EEAE98F5
F2EA9F784309D3AF7E3EB54C438E9319C6F1A042AD27A057F467A443107EAA0C
1925DBF53DCAC3785D54D10A82C6933A358F29EAF8A3B2FF8FFA7A910B03B15A
7D8B3690A8A11584E2CD5ECE5ADBAAD0A05BDF6C72904FD15D594F440C52B3E5
41F27164D96DD2413BDCBE2BE100FF0AA5D03AF17822016B8A4ABDD957013F95
926080827AADE64A8D2C6E21E192557AC9ABB2F5A2B22077EA244F862027BA01
C850ACE0F4656584970008FA713BCF763F29D5E07EAA4258BDA0292B7F3C9E19
3573C8C3392444BD85A36EDD0857B52217BF012A49B96C2A1C2DDBB632624EF7
E8A4A3BC1129CA749D1A899A5ED7F59BABDAE7A423218942591E6E76337412E6
DDED40543D2A54D76F58447732AF440F256EAB185C0DA1D59EAADEAD386F831C
688F9E9244A508608F1ECA0D3BFE54E877619A75E779156E0142F3557A8B0655
C57450CE4B5635E571242750E3742BF4A436AF878A6A5F10CC4E959ED6646076
9D61E80152E030BD89F2C9AFF375D8566BEBEEC202F7B3D3D3999C02E8E3B0EA
DC5B706E55D29C95AD5D90E9AE2D6C57C6DDADDB14DBFE50F775BFBA231BDB38
B2ABDA320F4BBFB347A040895E9E59DCCB0B711DB2C23B947A5459D7AAAA51E0
70109DEF926D16352529B5ED15AC94EAB95224534FA8871E769F80F61C1D454B
8792945A9ED5D4F26E215E92BF06FD1197BC3ED6A773BF04C93604DF2F19D62E
2123B189094EB0862F0C38560F7B5CCE6B0B7EE8110D57B5BF8336097823897C
A30234FBEABAC70F0A5E94C3659144EEC334EEBBEB5F5DE32F990F016DCED132
89D4877435E29660DA7D80A7F63F89C83D485C5FB0D202D74EAE63054AD58312
955D055FB5FDF0A1727DBD12E3D5EB19BC57AFA7B0FF99D9BCEA1C50AFFF0F67
42673D8F43A87A3D1C840C550833DC82DBA02A6C78246A786A93BFF3E851AF97
1F43EAF59A2349BD8E95D86D99F5628CAAADF3E22D8FD6DBAB37087A43DB43CA
87932A623C058B5278BCBA18A51E647DC5803F2CED3FFB03F2FEFF93B4FF9F27
BB57428973D49FE8C861555E8FCA91FFE4E9BDCC83CF9585F565FA4AAE8F5E46
2BCB1B7D99FF11457F11000EB061F14CBB93641E1183802C49F37A6E19C01824
F799045979F817ACAACC1E8EE53AB957B7F1A8E420FB0FA43F5306684E90DA1B
2F2A7F4745622E1D088E9493F211FD3A848FA26EA13CDBDE3F121E1D3E9DFC43
0A05510C1890F6160BDA6A800AADD7EBF0E6BC2D153C0EA9458F54FA50C6A940
C26A92592F4799711CD1282F13009D76A0F26278EF4B08FB964BE7DC29295E9D
B03C9A5DD4816A1D4597B7FD84BD0FFE4B0AD9F508D53361BB2A508DD2BB750C
D2A963988FD63134B0C5E2651F897044535FECAB43DFF95C898B1FADE894F590
8AB365BE69CA364F556BCA579EFA5F777AFC55A7283D6D05FFA8D8D464AF5A58
F13A041043F4A4CCB3C814641ED34C16E6BAEF483B0541048D907EAAD2756874
590252C32AE050B9FAA922ED2F104B5495B263094A29A849ACD46250B32DD58F
E61DADFAFD2C683A2A86962F6B21F09C0A2387D56B5E5893A7FA55BDA90098F2
8D9176630F86776E41723BFB9252D1D16CD68729E3693FA83C63402F329BB6C8
77E1BD7BFF375FA08FFE972FEC67FF4BB3BDEA6B2CB06B3285DE85DB094561F1
32578D2035201FACF701850BF916707BD0B4B1D5D15ECD9E5D9922BF5DDFE3A2
CBD535C957D6957DB0B2CE7AD033DA3BCC988F96A2A9516793E5FF0F60F4C5C5
55A3F646094ACFA270FDB4ADAD14C95B55AE0A47047816F59D62C5CA2058FE23
B51DB3A7B6D3A8EB2931D15DB905C5F381EF11084EF48F6891BF505E5D57EA11
F4EA9AD2A3F21AC49CBEBABC04DB52865FFDEBA92CEC7FE130739CA6287FE0E3
2D4C5345F2530E52FB66E9288DF3BDFC51C4C19B5ED3CFD7D7457DE6A2B87F0D
27F992FEF4E2CB171444F241D35384559122BBDF86BB2C1A46ABB478CFB878E3
382BBFAAC3E26CB7DABDD58717F285E4C352C7CF69B80A377FCAEAF7395F20F1
EEEEEEAC78B9F92C496F4690380E6FD2E476970DCB85A46FEF9274BD9CA4FADD
EE501A5BE71FFD1709D984F1BA92B28DF251F13F28E26DD13C5C435E78961EF2
FE281F267E80AE572E42659FEAF3757273CCFB42471C3A5EC9596E47CDB99FEF
8B99160CEFA2E8EB996404F9475E703F9A67F15F6FFE0EE35EC87558CDF390D1
E52D64DE71B8966C2A6F3D085D8E6AC2DA5EEB64384502F2DF3C374E8E2B3400
00
}

Here's a version of the "Catch Game" from earlier that replaces buttons with graphics, and adds a sound effect. You can create any game initially this way, simply using buttons, and later replace the buttons with more interesting graphic images, add sounds, etc.:

REBOL [title: "Feed The Gator"]
bird: load to-binary decompress 64#{
    eJzF2LuL4kAcwHG3shBURHDRRW3E+wuuPOEaC9lGC60EXyCClQjiM4qdzVWWVjYW
    iiA2Cj7wPOzEThALG7HYRixF737EU/bMTTLGzOQLgxCTyaeYxMTv77/0CravML7A
    +AbjDcaL4pXd/gO+/6m+jGu/n6vRaDAMY7fbTSaTzWa7zOn3+xm2zWbz5Pw8fSzF
    HTcajYLBoAIjn8/XbDalVV963J5KpXDIdwUCgV6vJ6tdhPour9crh/15+K1QKDSZ
    TER4t9ttsVhUsJcpnr3b7UoI/1wsFiuVSsw12HI+nz+fulAoVCoV7oF49sFgQAjO
    TaVSwRmPxyOQjUYjz54Oh0PIPh6PqcGhVqsluI/Vak2n08PhkNc+nU4pePGDlb5e
    r288tL3f78tt/ZtGo8lkMlwh2q5Wq4WnJZZSqYRreLnkWxYIuyyrJZFIZLPZxWLB
    48WwwzQ01ZFIBNMrZJ/NZtTUuVxOhBpt12q1FNSwLEWrEXZYbqTVtVrtSTXCbjab
    yanhuV4S9b/21Wp1OBzIwaPRaKfTkRDO2vP5PCHvrWQyKa36aj+dToTIHo+n3W6T
    UF/tl09425TEa7FYnE4nPCuRI3PskF6vF4F1u93lchmwu92Oghdhh3Q63UNwae8b
    j8a5RxoMBkx4vV6XhXzrf7+rOPBqtUofexfiWSwcDvPAKRtR8b43xePxO7XL5aJG
    Ewzvf4L9fg8v8fP5nDTnoT6WfwB19HCVxhQAAA==
}
alligator: load to-binary decompress 64#{
    eJxd0s1KW0EUB3C1K+lDuHVjXWSnkAvuXfgCBRG7bBeFlormPkKXQhuaN9BNxYUm
    UxF00UUWhUoIcRQXoYTkUm7T8Tof/845Z0o/5nL43XPmn+HmJmvrzx7N8KrHWoy1
    HGs71my8aC2mfVpzf25nAMQKaAe69zqZLbEu/8hWaf+31u+JlnM3NXtK6kt7xBrL
    OYVyTzSpNx0yQ3FA5iiekA3oV9Hg8had4x6yjHLefUKD9Ajk/RR4SvkN4Dnl5xEc
    9Y9lH1fgPAxCEF0yXCcn/+77/3VJL1Zt0Y7/nv9AxZa5rchuZi3ZUiXPc2VO2IEp
    k4aeF4PhS3Y0pPxP3JU0vwFYYwHyHGIrQp4Npa8ugSJa7ou3x0A3erEKT71/I3O/
    K3nnVxYMv78z7uHjL0YGmPei/sxq3EmPMply4O+D13Biwe8JL87RYJvicsewh40R
    +yHf7LDYYpuoXZMDv8p9L6g62beK8/1Ri/1itPTQnOsXb8XDrqgL+bwapr7kc3vw
    E+7Vw1eZhzXym/I75Ah2l1VY4bkWxxnekVOEC/J7Bj7nPkf6/4txRX8BGfoeSmYD
    AAA=
}
gulp-sound: load to-binary decompress 64#{
    eJxtlHtMU1ccx6vGR3kWLMWpm5pskc2pi3MkqFNYJsh0oigV5KEULK2lpdDb9vbe
    9r56W1pogSKltBQqSGGAlYeimAlDkU1H1KgssrhBmFNhjAECHcZl2bVo1LjfyTm/
    nO/vdf45n9joyMjphTTaoQj2Tq4QWB1Io9HmUSt8Fc2z59GWUEp6KpB6k8pC3zJs
    7nzTvXbB/sdhmMdjcx5744q9SkHf1l4f/LLhq+K33jZnOI6/3gZBVAjq8Qj6Mkzl
    ek5KwBEpL11saGyrANhx4kKHzUBgmFKhUKKIAgBAlNRqNaSayiO1pEqeLeTzxZjJ
    nA+KAK2ByBaidle1XoGVnrLjx3kSlULEOSaBIBmkMZkL9Lo8owYUZYqyRLwMMVJo
    NqgkoL7MQoqOiXUVzpPl1a7OiyXS6K2RBxJiE1KgitrTteYyq0ktStgbE3MwTUha
    7BXFmDxHjpGITE5Unu++dLaupc2UFTLfd5PEZi8xObp7W/Ts8N3Jx3npXMjqarQA
    8XFHpJBUeAwscNRZ8wwF1a7auvIzVWRCwCL/XY6B4c5CWGYol+8LWfk52X63t1Ka
    mnw0Izk+XkCcbDpVKueKUdvpRofVfulms3TlIh8mODQ7fjpx2359ERTFZB3u+Gt2
    MH+zr8+mgwJMZ2/vajYeDt2aYr72U7eNUDf+es+yYQFzQcQvs+MTmQH0zaTTsued
    j/HHT2b6vloc6L0+71Z/hw2RykVHvt6bVtTZd+ecraCq3sT28fNnFD4bdQ/sWhwQ
    xHG14HH7iHtPpi6ELgn0CnFNTQybdwSviYLs39TYyqwN565cq+evoQctDvv+6cjT
    ns/ogQGH6r4tVwh0PWMjzq1egb5rqqfHJhq3+Pt9kOW61l2jh3TOG0Pt/BV01sL9
    A+7H/5xi+TGWpl/pq0cScxruP2qI8g70W2Zxj7ovb/cJYCS0D49dMXC5up4H13OC
    vFkLEodnhv8t8WUE+AsHn9w27totPj94XbXCZ6l31uj45MOkJUz65tbJiZGahJ1p
    rv6fcZYXa/7eodlHtLplXqyFkXenJ7qSwz4VNlxuTlrK8NvYNDPqrl9LDcq4Pzk5
    aIyNFpdftITSmfQP256NuH9jL2L6MhVDk2Mdiojt8WBZniB8bWha7f2JOyf2BLO2
    4b1jk301pAxAeFtWegfvbJgan36ofc/Hj7G/cuCPvlaYx04SZPCT4lKExvqLbRXg
    wS+jBZbv+vuuNhWDieuCAoN3EP0zU5PnOcu9vEO41Tdu9zp1AOc4N4PDPpSYlV/T
    dtaBcWJi+YbWH+/e7HAoD6zyZyyP0t9z//1nq2Cjj/8nKaXtP1ytOyHn8TkZ3DRu
    ugjWWU9WFqMivhCznunq6W42Z4Yt8wrYwGt++NR9x5K0/t33I0TFrZ1tTj3ATU7l
    iUFQli2SwFrTiaJ8jdZgrmpou+CqkOz+yD94XZK568Hvt5rwo3u+iEmR6IttZUYS
    yBRkyhFCDcnEmdkArMkvsTlqapx1VeWGnCNRYWHhidKi+pbW+tJ8pVwuQ3G1BoVz
    eOkcbg6caywoyMWVIKTEqf9ozNPrcrUYLMuRABBO6nUkppACAIzpqLQ8LYmpIBAE
    FRAEwbASwQmCwFSwgpIgJarW6inTUfUakiTVOAJTMkJoNCSuAmVSQCqHUdwTec4X
    tRrHVEpYqaKMIg9OqNVqAvcgh9RoczUkgSKeGMUlRKWkBqpQah6Gzkko/oJjOE7g
    L2FFAc2DM8zTjHoZDKsw4vlEDwyRF5xUKZVUj7lagsCxOfYR/wHwfuhW/AUAAA==
}
alert "Arrow keys move left/right, up goes faster, down goes slower"
random/seed now/time   
speed: 9   score: 0
view center-face layout [
    size 600x440   backdrop white   across
    at 270x0 text "Score:"  t: text bold 100 (form score)
    at 280x20  y: image bird
    at 280x340 z: image alligator
    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
            insert port: open sound:// gulp-sound wait port close port
        ]
        t/text: (form score)  show t
    ]]]
]

Here's another simple game example which illustrates several useful GUI techniques:

REBOL [Title: "Collect the Boxes"]
random/seed now
start-time: now/time
level: to-integer request-text/title/default "Number of Boxes?" "10"
gui: [
    size 600x440  backdrop white
    at -99x0 key keycode [up]    [p/offset: p/offset + 0x-10 show p]
    at -99x0 key keycode [down]  [p/offset: p/offset + 0x10  show p]
    at -99x0 key keycode [left]  [p/offset: p/offset + -10x0 show p]
    at -99x0 key keycode [right] [p/offset: p/offset + 10x0  show p]
    at -99x0 box rate 0 feel [engage: func [f a e][if a = 'time [
        foreach f system/view/screen-face/pane/1/pane [
            if  f <> p [
                if within? (f/offset + 10x10) p/offset 30x30 [
                    remove find system/view/screen-face/pane/1/pane f
                    show system/view/screen-face/pane/1/pane
                ]
            ]
            if (length? system/view/screen-face/pane/1/pane) < 8 [
                alert rejoin ["Your time: " now/time - start-time]
                quit
            ]
        ]
    ]]]
]
for counter 1 level 1 [
    append gui [at random 590x420 box 10x10 random 255.255.255]
]
append gui [p: btn red 20x20]
view center-face layout gui

Here is a version of the Windows webcam program from earlier in this tutorial. This version was written for REBOL/face, and includes all the common avicap32.dll constants. It also contains the "do" files required in REBOL/face (they should be deleted if using REBOL/view). It also contains a function that can be used to hide and unhide windows:

REBOL []

do %gfx-colors.r
do %gfx-funcs.r
do %view-funcs.r
do %view-vid.r
do %view-edit.r
do %view-feel.r
do %view-images.r
do %view-styles.r
do %view-request.r
do %view.r

;WM_CAP_START: 0x400
;WM_START: to-integer #{00000400}
WM_CAP_START: 1024
WM_CAP_UNICODE_START: WM_CAP_START + 100 
WM_CAP_PAL_SAVEA: WM_CAP_START + 81 
WM_CAP_PAL_SAVEW: WM_CAP_UNICODE_START + 81 
WM_CAP_UNICODE_END: WM_CAP_PAL_SAVEW 
WM_CAP_ABORT: WM_CAP_START + 69 
WM_CAP_DLG_VIDEOCOMPRESSION: WM_CAP_START + 46 
WM_CAP_DLG_VIDEODISPLAY: WM_CAP_START + 43 
WM_CAP_DLG_VIDEOFORMAT: WM_CAP_START + 41 
WM_CAP_DLG_VIDEOSOURCE: WM_CAP_START + 42 
WM_CAP_DRIVER_CONNECT: WM_CAP_START + 10 
WM_CAP_DRIVER_DISCONNECT: WM_CAP_START + 11 
WM_CAP_DRIVER_GET_CAPS: WM_CAP_START + 14 
WM_CAP_DRIVER_GET_NAMEA: WM_CAP_START + 12 
WM_CAP_DRIVER_GET_NAMEW: WM_CAP_UNICODE_START + 12 
WM_CAP_DRIVER_GET_VERSIONA: WM_CAP_START + 13 
WM_CAP_DRIVER_GET_VERSIONW: WM_CAP_UNICODE_START + 13 
WM_CAP_EDIT_COPY: WM_CAP_START + 30 
WM_CAP_END: WM_CAP_UNICODE_END 
WM_CAP_FILE_ALLOCATE: WM_CAP_START + 22 
WM_CAP_FILE_GET_CAPTURE_FILEA: WM_CAP_START + 21 
WM_CAP_FILE_GET_CAPTURE_FILEW: WM_CAP_UNICODE_START + 21 
WM_CAP_FILE_SAVEASA: WM_CAP_START + 23 
WM_CAP_FILE_SAVEASW: WM_CAP_UNICODE_START + 23 
WM_CAP_FILE_SAVEDIBA: WM_CAP_START + 25 
WM_CAP_FILE_SAVEDIBW: WM_CAP_UNICODE_START + 25 
WM_CAP_FILE_SET_CAPTURE_FILEA: WM_CAP_START + 20 
WM_CAP_FILE_SET_CAPTURE_FILEW: WM_CAP_UNICODE_START + 20 
WM_CAP_FILE_SET_INFOCHUNK: WM_CAP_START + 24 
WM_CAP_GET_AUDIOFORMAT: WM_CAP_START + 36 
WM_CAP_GET_CAPSTREAMPTR: WM_CAP_START + 1 
WM_CAP_GET_MCI_DEVICEA: WM_CAP_START + 67 
WM_CAP_GET_MCI_DEVICEW: WM_CAP_UNICODE_START + 67 
WM_CAP_GET_SEQUENCE_SETUP: WM_CAP_START + 65 
WM_CAP_GET_STATUS: WM_CAP_START + 54 
WM_CAP_GET_USER_DATA: WM_CAP_START + 8 
WM_CAP_GET_VIDEOFORMAT: WM_CAP_START + 44 
WM_CAP_GRAB_FRAME: WM_CAP_START + 60 
WM_CAP_GRAB_FRAME_NOSTOP: WM_CAP_START + 61 
WM_CAP_PAL_AUTOCREATE: WM_CAP_START + 83 
WM_CAP_PAL_MANUALCREATE: WM_CAP_START + 84 
WM_CAP_PAL_OPENA: WM_CAP_START + 80 
WM_CAP_PAL_OPENW: WM_CAP_UNICODE_START + 80 
WM_CAP_PAL_PASTE: WM_CAP_START + 82 
WM_CAP_SEQUENCE: WM_CAP_START + 62 
WM_CAP_SEQUENCE_NOFILE: WM_CAP_START + 63 
WM_CAP_SET_AUDIOFORMAT: WM_CAP_START + 35 
WM_CAP_SET_CALLBACK_CAPCONTROL: WM_CAP_START + 85 
WM_CAP_SET_CALLBACK_ERRORA: WM_CAP_START + 2 
WM_CAP_SET_CALLBACK_ERRORW: WM_CAP_UNICODE_START + 2 
WM_CAP_SET_CALLBACK_FRAME: WM_CAP_START + 5 
WM_CAP_SET_CALLBACK_STATUSA: WM_CAP_START + 3 
WM_CAP_SET_CALLBACK_STATUSW: WM_CAP_UNICODE_START + 3 
WM_CAP_SET_CALLBACK_VIDEOSTREAM: WM_CAP_START + 6 
WM_CAP_SET_CALLBACK_WAVESTREAM: WM_CAP_START + 7 
WM_CAP_SET_CALLBACK_YIELD: WM_CAP_START + 4 
WM_CAP_SET_MCI_DEVICEA: WM_CAP_START + 66 
WM_CAP_SET_MCI_DEVICEW: WM_CAP_UNICODE_START + 66 
WM_CAP_SET_OVERLAY: WM_CAP_START + 51 
WM_CAP_SET_PREVIEW: WM_CAP_START + 50 
WM_CAP_SET_PREVIEWRATE: WM_CAP_START + 52 
WM_CAP_SET_SCALE: WM_CAP_START + 53 
WM_CAP_SET_SCROLL: WM_CAP_START + 55 
WM_CAP_SET_SEQUENCE_SETUP: WM_CAP_START + 64 
WM_CAP_SET_USER_DATA: WM_CAP_START + 9 
WM_CAP_SET_VIDEOFORMAT: WM_CAP_START + 45 
WM_CAP_SINGLE_FRAME: WM_CAP_START + 72 
WM_CAP_SINGLE_FRAME_CLOSE: WM_CAP_START + 71 
WM_CAP_SINGLE_FRAME_OPEN: WM_CAP_START + 70 
WM_CAP_STOP: WM_CAP_START + 68

avicap32.dll: load/library %avicap32.dll
user32.dll: load/library %user32.dll

; Hide rebface console:

get-focus: make routine! [return: [int]] user32.dll "GetFocus"
hwnd-hide-console: get-focus
hide-window: make routine! [
    hwnd [int] 
    a [int]
    return: [int]
] user32.dll "ShowWindow"
hide-window hwnd-hide-console 0

view/new center-face layout/tight [
    image 320x240
    across
    btn "Take Snapshot" [
        sendmessage cap-result WM_CAP_GRAB_FRAME_NOSTOP 0 0
        sendmessage-file cap-result WM_CAP_FILE_SAVEDIBA 0 "scrshot.bmp"
    ]
    btn "Exit" [
        sendmessage cap-result WM_CAP_END 0 0
        sendmessage cap-result WM_CAP_DRIVER_DISCONNECT 0 0
        free user32.dll
        quit
    ]
]

; Set window title:

set-caption: make routine! [
    hwnd [int] 
    a [string!]
    return: [int]
] user32.dll "SetWindowTextA"
hwnd-set-title: get-focus
set-caption hwnd-set-title "Web Camera"

find-window-by-class: make routine! [
    ClassName   [string!]
    WindowName  [integer!]
    return:     [integer!]
] user32.dll "FindWindowA"
hwnd: find-window-by-class "REBOLWind" 0

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"
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-result: cap "cap" 1342177280 0 0 320 240 hwnd 0
; 1342177280 in the line above is the value I got from
; BitOR(WS_CHILD,WS_VISIBLE) in two seperate development environments,
; but not sure if it will always hold true.
sendmessage cap-result WM_CAP_DRIVER_CONNECT 0 0
sendmessage cap-result WM_CAP_SET_SCALE 1 0
sendmessage cap-result WM_CAP_SET_OVERLAY 1 0
sendmessage cap-result WM_CAP_SET_PREVIEW 1 0
sendmessage cap-result WM_CAP_SET_PREVIEWRATE 1 0

do-events

Here's one final version of the web cam program, with a nicer save feature. In order for the save routine to work properly, this code should be saved to a .r script and run from there:

REBOL []

avicap32.dll: load/library %avicap32.dll
user32.dll: load/library %user32.dll
get-focus: make routine! [return: [int]] user32.dll "GetFocus"
set-caption: make routine! [
    hwnd [int] a [string!]  return: [int]
] user32.dll "SetWindowTextA"
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"

view/new center-face layout/tight [
    image 320x240
    across
    btn "Take Snapshot" [
        sendmessage cap-result 1085 0 0
        sendmessage-file cap-result 1049 0 "scrshot.bmp"
        save-path: first split-path system/options/script
        view/new center-face layout [
            image load join save-path %scrshot.bmp
            btn "save" [
                (write/binary 
                    to-file pp: request-file/save/file %photo1.bmp
                    read/binary join save-path %scrshot.bmp
                )
                alert join "Saved " pp
                unview
            ]
        ]
    ]
    btn "Exit" [
        sendmessage cap-result 1205 0 0
        sendmessage cap-result 1035 0 0
        free user32.dll
        quit
    ]
]
hwnd-set-title: get-focus
set-caption hwnd-set-title "Web Camera"  ; title bar
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
do-events

This is a sound synthesizing example derived from the quick hack demo by Cyphre:

REBOL []

wait 0 
octave: ["c" "cs" "d" "ds" "e" "f" "fs" "g" "gs" "a" "as" "b" "c"]
notes: copy [] 
oct: -1 
repeat n 12 * 6 [
    if (n - 1 // 12 + 1) = 1 [oct: oct + 1] 
    insert tail notes reduce [
        to-word join pick octave n - 1 // 12 + 1 oct 440 / (
            2 ** ((46 - n) / 12)
        )
    ]
] 
make-sound: func [type freq ln /local tone freq2 result] [
    switch type [
        square [
            freq: to-integer 22050 / freq 
            tone: head insert/dup copy #{} to-char 0 freq 
            result: copy #{} 
            freq2: to-integer freq / 2 
            repeat n freq2 [
                poke tone n to-char 0 
                poke tone n + freq2 to-char 255
            ] 
            insert/dup result tone ln / freq 
            return result
        ] 
    ]
] 
make-pattern: func [
    tracks 
    /local out snd-tracks t tempo mix
] [
    out: make sound [
        rate: 22050 
        channels: 1 
        bits: 8 
        volume: 0.5 
        data: #{}
    ] 
    snd-tracks: copy [] 
    loop (length? tracks) / 2 [
        insert tail snd-tracks copy #{}
    ] 
    t: 0 
    tempo: (60 / 120)         ; SET THE TEMPO HERE
    foreach [inst track] tracks [
        t: t + 1 
        repeat n length? track [
            either track/:n = 'xx [
                insert/dup tail 
                    snd-tracks/:t to-char 128 to-integer 22050 * tempo / 4
            ] [
                insert tail snd-tracks/:t 
                make-sound inst select notes 
                track/:n to-integer 22050 * tempo / 4
            ]
        ]
    ] 
    out/data: head insert/dup copy #{} to-char 0 length? snd-tracks/1 
    mix: array/initial length? snd-tracks/1 0 
    foreach track snd-tracks [
        repeat n length? snd-tracks/1 [
            poke mix n mix/:n + track/:n
        ]
    ] 
    repeat n length? snd-tracks/1 [
        poke out/data n to-char to-integer mix/:n / ((length? tracks) / 2)
    ] 
    return out
] 
soundtrack: make sound [
    rate: 22050 
    channels: 1 
    bits: 8 
    volume: 0.5            ; SET THE VOLUME HERE
    data: #{}
]

; Here are the notes to be played.  All tracks should have the same
; number of notes.  The note names for the musical alphabet are:
; c2 cs2 d2 ds2 e2 f2 fs2 g2 gs2 a2 as2 b2.  Use "xx" for rests.

; -----------------------------------------------------------

tracks-1: [
    square [
        c1 cs1 d1 ds1  e1 f1 fs1 g1  gs1 a1 as1 b1
        c1 xx cs1 xx   d1 xx ds1 xx  e1 xx f1 xx fs1 xx
        g1 xx gs1 xx   a1 xx as1 xx  b1
    ] 
    square [
        e2 f2 fs2 g2  gs2 a2 as2 b2  c3 cs3 d3 ds3
        e2 xx f2 xx   fs2 xx g2 xx   gs2 xx a2 xx as2 xx
        b2 xx c3 xx   cs3 xx d3 xx   ds3
    ]
] 

; -----------------------------------------------------------

; This initiates the playing:

p1: make-pattern tracks-1
insert/dup tail soundtrack/data p1/data 2
; p2: make-pattern tracks-2
; insert/dup tail soundtrack/data p2/data 1
; the last # is the number of times to repeat the soundtrack
sp: open sound:// 
insert sp soundtrack 

; Here are some start-stop controls:

ask "press enter to quit"
; wait sp
close sp

This script by Volker Nitsch demonstrates how to use the "set-it" func of the GUI list style:

stuff: copy []
view layout [
    lst: list [across info info] 400x400 supply [
        either count > length? stuff [face/text: "" face/image: none] [
            lst/set-it face stuff index count
        ]
    ]
    with [probe words source set-it] ; get some hints
    button "add now" [
        append/only stuff reduce [mold 1 + length? stuff mold now/time]
        show lst
    ]
]

The following code demonstrates how to check for async keystrokes (including arrow keys) in the REBOL shell:

print ""
p: open/binary/no-wait console://
q: open/binary/no-wait [scheme: 'console]

forever [
    if not none? wait/all [q :00:00.30] [
        wait q
        qq: to string! copy q
        probe qq
    ]
]

Don't forget to look at all the scripts at rebol.org - not just in the scripts section, but also in the email and AltME archives. It's a treasure trove of working code examples and answers to virtually any coding problem!

9. Learning More About REBOL - IMPORTANT DOCUMENTATION LINKS

A very old edition of this text with several hundred screen shot images is available at http://musiclessonz.com/rebol_tutorial-images.html). If you're completely new to programming, that text may offer some helpful simple perspective.

The tutorial at http://www.rebol.com/docs/rebol-tutorial-3109.pdf provides a nice summary of fundamental concepts. It's a great document to read next. To learn REBOL in earnest, read the REBOL core users manual: http://rebol.com/docs/core23/rebolcore.html. It covers all of the data types, built-in word functions and ways of dealing with data that make up the REBOL/Core language (but not the graphic extensions in View). It also includes many basic examples of code that you can use in your programs to complete common programmatic tasks. Also, be sure to keep the REBOL function dictionary handy whenever you write any REBOL code: http://rebol.com/docs/dictionary.html. It defines all the words in the REBOL language and their specific syntax use. The dictionary is also helpful in cross-referencing function words that do related actions in the language (great when you can't remember a function name you're looking for). Along the way, read the REBOL View and VID documents at: http://rebol.com/docs/easy-vid.html , http://rebol.com/docs/view-guide.html , http://rebol.com/docs/view-system.html , http://www.rebol.com/how-to/feel.html , http://www.pat665.free.fr/gtk/rebol-view.html , and run the script at http://www.rebol.org/download-a-script.r?script-name=vid-usage.r. Those documents explain how to write Graphical User Interfaces in REBOL. Once you've got an understanding of the grammar and vocabulary of the language, dive into the REBOL cookbook: http://www.rebol.net/cookbook/. It contains many simple and useful examples of code needed to create real-world applications. When you've read all that, finish the rest of the documents at http://rebol.com/docs.html.

Beyond the basic documentation, there is a library of hundreds of commented REBOL scripts at http://rebol.org. There's also a searchable archive of the mailing list and AltME (community forum) containing several hundred thousand posts at rebol.org. That archive contains answers to many thousands of questions encountered by REBOL programmers. Rebol.org is an essential resource! There are numerous other web sites such as http://www.codeconscious.com/rebol, http://www.rebolforces.com (duplicated at http://www.rebolplanet.com), http://www.reboltech.com/library/library.html, http://www.fm.vslib.cz/~ladislav/rebol, http://www.compkarori.com/vanilla/display/index, http://www.rebol.net, http://reboltutorial.com, http://blog.revolucent.net/search/label/REBOL, http://www.reboltalk.com/forum, http://anton.wildit.net.au/rebol, http://rebolweek.blogspot.com, http://groups-beta.google.com/group/Rebol, and rebolfrance (translated by Google) that provide more help in understanding and using the language. Don't miss Carl Sassenrath's personal blog, discussions about REBOL3, alpha downloads of REBOL3, and REBOL3 documentation. For a complete list of all web pages and articles related to REBOL, see http://dmoz.org/Computers/Programming/Languages/REBOL/.

Don't forget to click the rebsite icons in the "REBOL" and "Public" folders, right in the desktop of the REBOL interpreter. Right-click any of the hundreds of individual program icons and select "edit" to see the code for any example. That's a great way to see how to do things in REBOL.

Copyright Nick Antonaccio 2005-2010, All Rights Reserved

MakeDoc2 by REBOL - 11-Jul-2010