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:
- 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.
- 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.
- 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.
- 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.
- 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.).
- 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:
- 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
- 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.
- Type "do %/c/webcam.r" into the REBOL interpreter.
- 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).
- 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
- 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 { }
{<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:
- "any" - the rule matches the data zero or more times
- "some" - the rule matches the data one or more times
- "opt" - the rule matches the data zero or one time
- "one" - the rule matches the data exactly one time
- an integer - the rule matches the data the given number of times
- 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:
- Assign a word label to the box in which the drawing takes place (the word "scrn" is used in the following examples).
- Create a new draw block in which the characteristics of the graphic elements (position, size, etc.) are changed.
- Assign the new block to "{yourlabel}/effect/draw" (i.e., "scrn/label/draw: [changed draw block]" in this case).
- 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:
- Assign a rate of 0 to a GUI item in a 'view layout' block.
- 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.
- 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>} {<\/textarea>}
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:
- Modern look and feel.
- 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.
- Simple and elegant syntax (similar to VID).
- Full documentation and demo code for all widgets.
- Super simple notation to handle automatic alignment and layout of widgets in resized windows.
- 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.
- Automatic handling of window close events.
- User assignable function key actions.
- Easy, automatic handling of multiple user languages.
- Well designed object structure to access every widget, function, and feature (and containing all necessary help information, built in).
- 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 211×297 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:
- 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.
- 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.
- 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!
- 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.
- 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!
- 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:
- 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.
- 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...".
- 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.
- 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.
- 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#{
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
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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
}
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
|