REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 054

SimplePaint: Part I

Occasionally, my boss at the printshop where I work brings one of his children in for a few hours. The youngest now is age 5, but a few years ago the oldest was that age. Kids that young have short attention spans, and it was always a challenge finding something entertaining to keep them out of trouble.

Drawing always amused them, and we discovered they were fascinated by drawing on the computer. So I'd load up a paint program on an extra Mac we have and let them play around. Initially I tried Adobe Photoshop, since that's the bare minimum of a paint program in my book. Of course that was far too complicated for the girls, so I switched to ClarisWorks. That was sort of satisfactory, but I discovered I had to keep a close eye on them: without supervision they'd click outside the window, bringing up the Finder, and then they'd create all sorts of havoc, moving folders around and typing gibberish for file names.

For three or four seconds I considered searching for a free "KidPaint" type program on the Internet. But I was too lazy to find and test all those programs. Instead, the idea quickly hit me: I'd write my own.

I loaded up REALbasic and in just a few minutes I had a very basic paint program. It was so much fun I quickly added some cool sophisticated features. The end result is a very simple paint program specifically designed for kids. Sure, there's probably cooler or better stuff out there, but this did exactly what I wanted and the price was right.

Program Specifications

Since this program was targeted for children, I had a narrow range for my specifications. I came up with the following mental list of criteria:

  • No menu bar
  • No floating palettes or windows
  • All commands should be one keyboard letter or mouse click
  • Saving shouldn't require navigating a save dialog
  • No dialog boxes at all, in fact
  • No way to switch applications or accidently quit

I decided that since the girls found the whole "tool palette" thing confusing (it's fundamentally modal), I wouldn't offer the traditional drawing program options of letting you draw circles, lines, or rectangles. Instead, there'd only be a pencil tool. In this program there's only one mode: painting with pixels and that's it.

Since the girls rarely wanted to go back and edit their creations, I decided not to bother creating a way to reopen previous drawings. The girls would draw something, save it, erase the screen, and start again.

There was also no point in creating a way to print the pictures: if they wanted, I'd print the saved images for them out of Photoshop.

A Blank Screen

In the old days of REALbasic, hiding the menubar was a difficult trick requiring system calls or a plugin. With REALbasic 4, however, it's easy. Just uncheck the MenuBarVisible property of a window!

Create a new project. Rename window1 as paintWindow and give it these settings:

Note that checking the HasBackColor property gives the window a white background, which is what we want since that will be our drawing area.

Go ahead and save your project in a "Simple Paint" folder with "SimplePaint.rb" as the name. (Note that ".rb" is the extension REALbasic uses for project files under Mac OS X, so it's good to use that even under earlier operating systems.)

Remembering the Drawing

Since we'll be doing various kinds of manipulations of our drawing (saving, editing, etc.), it's a good idea to store it inside a picture variable.

Open paintWindow and add a new property (Edit menu, "New Property") p as picture. As long as we're here, lets add some other properties we'll need.

To get SimplePaint working as a bare bones paint program, we don't have to define these right now, but we're thinking ahead. The c property will hold the color we're currently painting with. The xWidth and yWidth properties represent the size of the paintbrush we're using. Eventually we'll add a way to modify these properties so the child can pick different colors to draw with and change the size of the paintbrush.

Initializing

Before we can begin drawing into our picture variable, we must initialize it (in technical terms, allocate [set aside] memory [storage] for the variable). We do that with the newPicture method. The picture must be as large as our window (which is as large as the main screen, since we checked the window's FullScreen property). We also want the picture to be full color, so we use 32 as newPicture's parameter (meaning the picture will be 32-bit color picture). (On machines without much memory, you could get by with 16 or 8, though 8-bit color is only 256 colors.)

Since this must happen early in the program's launch, let's put it in our drawing window's Open event. Put this code in paintWindow's Open event:

  
p = newPicture(screen(0).width, screen(0).height, 32)

if p = nil then
beep
msgBox "Sorry, there's not enough memory to run SimplePaint."
msgBox "I must quit now."
quit
end if

self.backdrop = p

xWidth = 9
yWidth = 9

Note that if there isn't enough memory for p, we quit the program. Not exactly the most polite way to quit, but better than a crash. (If you wanted, you could try to allocate the picture at 32 bits, and if p = nil then try again at 16 bits. Just make sure you again check that p isn't nil after that.)

Once we've got a valid p, we assign that to paintWindow's backdrop property. That means whatever the picture p looks like will be the background of paintWindow. This way we don't have to do any drawing or redrawing of p -- that happens automatically whenever paintWindow needs it.

Finally, we set the default sizes of our xWidth and yWidth values.

Watching the Mouse

All painting programs work on the same basic principle: when the user clicks the mouse button, a pixel is drawn at that click location. Ours is no different.

Go to the MouseDown event of paintWindow and add this:

 return true 

This just tells REALbasic that we'll be handling the MouseDown event. The default response is to return false, meaning that we're ignoring the MouseDown. By returning true, REALbasic will now allow the MouseDrag event to happen. And that's where we'll do most of our work.

In Detail

Wondering why we use MouseDrag instead of MouseDown?

That's because MouseDrag is an event that happens repetitively, while MouseDown only happens once, when the mouse button is initially clicked. Once the mouse button is down, MouseDrag reports the current coordinates of the mouse cursor as the user moves the mouse around. That's the data we need.

In the MouseDrag event, we'll add this code:

  
dim x2, y2 as integer

x2 = x
y2 = y

drawPixel(p.graphics, x2, y2)

You might wonder why we bother declaring the x2 and y2 variables -- wouldn't it be easier to just pass x and y on to drawPixel?

Yes, it would, but we're again thinking ahead: in the future we may want to manipulate those x and y properties.

The final line passes a graphics object (from p, our picture), and the drawing coordinates, to our drawPixel routine. What drawPixel routine you ask? The one we're about to create!

Drawing a Pixel

To actually get anything to draw, we need to create that drawPixel routine we just called. Add a new method (Edit menu, "New Method") and name it drawPixel. Put g as graphics, x as integer, y as integer on the parameters line. The code is fairly simple:

  
dim n1, n2 as integer

n1 = xWidth \ 2
n2 = yWidth \ 2

g.foreColor = c
g.fillOval(x - n1, y - n2, xWidth, yWidth)

self.refreshRect(x - n1, y - n2, xWidth, yWidth)

What are n1 and n2? They are the center point of the "pixel" that's going to be drawn. Remember, our "pixel" isn't actually one pixel in size: it's the size of xWidth and yWidth (which we set to 9 by default). We'll draw that extra-large pixel using REALbasic's fillOval method.

When REALbasic draws a circle, the coordinates where you start drawing that circle are the upper left of the box the circle is drawn in. For example, the red circle below is drawn by giving it the dimensions of the outer box (upper left corner and width and height).

In a sense, a circle is just a box with extremely rounded corners!

But for our purposes, we want the center of the circle to be the x and y coordinates where the user clicked. That means we must do a little math to adjust those drawing coordinates so they're half the width and height of the circle off from the original coordinates. So by starting to draw the circle to the left and up, the center of that circle is at the original coordinates.

Next, we set our drawing color to c and we draw the circle (oval, since the width and height could be different). Finally, we tell paintWindow to refresh (update) the area we just drew.

Simple, eh? Try it. The program works and will let you doodle:

But there's no way to change the drawing color, the size of the brush, or save the picture. We'll work on that next time.

If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Next Week

We enhance SimplePaint with a color palette and other features.

News

The development of REALbasic Developer magazine is proceeding well. Articles have been written and edited, and we're working on the layout of the first issue. Here's a sneak peek of what the cover of the premiere issue looks like:

Subscription pre-orders are now available at 25% off the normal price.

Letters

Several people wrote to tell me that our last project, Zoomer, does not work under Mac OS X. Well, it half works: the editField is magnified, but the text inside it is not. Apparently some of the bugs that used to exist with the drawInto method still exist under Mac OS X (in the past, drawInto had a bug where it would only draw the first few lines of the editField and not draw text that had been scrolled).

First, I want to apologize for not testing Zoomer under Mac OS X. That was an oversight. More and more people are using OS X for development and I'll try to make sure everything we do for RBU works for both platforms. At minimum, if something won't work, I'll let you know.

Second, if you'd like to get Zoomer working under Mac OS X, you can try this workaround. It won't fix the editField's text not being shown bug, but it will give you something to magnify.

Open the drawZoom method. Do a find/replace with these settings:

Click the "Replace All" button.

What this does is let Zoomer magnify the entire window instead of just editField1. That gives you some stuff to look at -- if you want, drag some more controls onto window1 to give you more things to zoom.

Next, Julian writes with a problem with popupMenus.

Hi,

Firstly keep up the good work - your column is excellent and very informative. I have been able to use many of your techniques for my own stuff - thank you.

I am trying to use PopUpMenus to navigate up and down a hierarchy of lists and data and have met a small problem.

When a PopUpMenu is populated dynamically from within a program it does not show any text until the listIndex is set, like:

 andSomeAfterPopup.listIndex = 0 

The trouble is that I do not want this update to trigger a 'change method.' I only want to trigger the change method code say:

 linkField.text = me.text 

when the user clicks on the PopUp and chooses a new value.

I have coded around it by setting a global 'gMouseWithinAfterPopUp' so the change code reads:

  
if gMouseWithinAfterPopUp = true then
linkField.text = me.text
else
do nothing
end if

And of course the MouseEnter and exit events flip the global.

This solves the problem but is not what I might call elegant.

Have I missed something here, or do you know of a better way?

Also I agree that much of a RB programme is self documenting but it might be an idea for a comment/description box at the bottom of each new Method and Property dialog box - with the facility to print/ not print the comment/descriptions. I tend to set up a 'dummy' method if I want to write a saga about a program.

Yours at sea

Jules.

The problem you are describing is very common. While your solution is one method, it's complex. A simpler solution, which is what I frequently use, is to create a "myChange" variable that tells my code it's a programmatic change, not a user action.

So your initialization event would look like this:

  
myChange = true
andSomeAfterPopup.listIndex = 0
myChange = false

Then, within the Change event, you have code like this:

  
if not myChange then
// Put whatever you want to happen
// when the user changes the popup here.
.
.
.
end if

I use this technique frequently when I implement an undo/redo system. That's because an undo system generally saves a user action in some kind of buffer (in order to possibly undo it later). The problem is when you need to redo a user's old action or restore data to its previous state via undo, that action will be regarded as a user's action: your program can't tell the difference between something you do programmatically (via code) or something the user is doing. But creating a boolean variable that tells the routine that this is a programmatic action means you can avoid doing what normally might happen when the user does that.

Still, this solution requires extra work on your part. What would be nice -- we ought to suggest it to REAL Software -- would be a built-in global boolean that would return true if a call to a routine was made programatically and false if it was made by the user. You'd still have to enclose your code with a check to this value, but you wouldn't have to worry about setting the boolean yourself.

As to your other comment, about having a description area within methods, it's not a bad idea. Until REAL Software adds that, most people add a dummy method to describe a class, something like this:


About the Column
REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers.

Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.)

About the Author
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband