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 053

Zoomer: Part II

Last week we started Zoomer, a little experiment to test out REALbasic's DrawInto command. Our goal is to create a virtual magnifying glass that will display one window enlarged inside another.

We set up the basics of our project and learned all about the difference between global and local coordinate systems, which are significant for this project. Today we'll finish Zoomer by creating the actual code that will display the zoomed window.

The drawZoom Method

We created the drawZoom method last time, but left the contents empty. Let's put some code in there now!

The first thing we'll do is adjust the size of zoomWindow to make sure it's not bigger than our zoomed in area. Why do this? Well, if we didn't, zoomWindow would attempt to display more of a picture then there actually was. For example, say our zoomed picture is 200 pixels wide but the window is 300. Our drawPicture command tries to fill the entire zoomWindow with the picture. Since the picture isn't big enough, random stuff from memory ends up being drawn as a picture. It doesn't really hurt anything, but it sure looks strange:

While there are other ways to prevent this from happening, I chose to keep things simple and not let the user make zoomWindow bigger than the zoomed area. Since the zoomed area is magnified in zoomWindow, the maximum size of zoomWindow changes based on the zoom percentage set by zoomSlider. (We set the maximum size via zoomWindow's maxWidth and maxHeight properties.)

Here's the code for drawZoom:

  
dim p as picture
dim x1, y1, zW, zH as integer

// Adjust zoomWindow size
zoomWindow.maxWidth = editField1.width * zoomAmount
zoomWindow.maxHeight = editField1.height * zoomAmount
if zoomWindow.width > zoomWindow.maxWidth then
zoomWindow.width = zoomWindow.maxWidth
end if
if zoomWindow.height > zoomWindow.maxHeight then
zoomWindow.height = zoomWindow.maxHeight
end if

zW = zoomWindow.width * zoomAmount
zH = zoomWindow.height * zoomAmount

// Allocate space for our zoomed picture
p = newPicture(zW, zH, 16)
// We should really check here to see if p
// is nil or not, but I'm too lazy ;-)
// We'll just assume there was enough memory to allocate p

// This draws the window into our picture object.
window1.drawInto p.graphics, 0, 0

// This part calculates what portion of the editField
// we're going to be drawing, based on the cursor
// position.
x1 = x - editField1.left
if x1 < 1 then
x1 = editField1.left
elseif x1 + zoomWindow.width / zoomAmount > editField1.width then
x1 = editField1.width - zoomWindow.width / zoomAmount + editField1.left
else
x1 = x
end if
y1 = y - editField1.top
if y1 < 1 then
y1 = editField1.top
elseif y1 + zoomWindow.height / zoomAmount > editField1.height then
y1 = editField1.height - zoomWindow.height / zoomAmount + editField1.top
else
y1 = y
end if

// This actual draws the zoomed picture.
// Note that zW and zH represent the magnified
// picture size.
zoomWindow.graphics.drawPicture p, 0, 0, zW, zH, x1, y1, zoomWindow.width, zoomWindow.height

After adjusting zoomWindow's size (if necessary), we then allocate space for p, our picture object. The size of p is the size of zoomWindow multiplied by zoomAmount. Since 16-bit and 32-bit pictures look almost exactly alike, we go with a 16-bit picture to save memory (that's the 16 in the newPicture line).

Once we've got our picture object, we use the drawInto command to draw window1 into the graphics object of p. The simplest way to understand the drawInto command is to think of it like taking a screenshot of the window. In our case, we're storing that screenshot into p -- and later we're drawing p in zoomWindow, except drawing it at a larger size. Note that when we use the drawInto command, we set it to draw at coordinates 0, 0 -- that means the upper left corner of window1 will be drawn at the upper left corner of p.

In the case of Zoomer, our goal was to only draw editField1 larger, not window1 in its entirety. That means we must constrain the "view" of zoomWindow so it only displays the enlarged editField.

We do this in two ways. First, we've made the size of p relative to the size of editField1 (certainly no larger). Second, we control what portion of p is drawn into zoomWindow by controlling where we begin drawing.

To understand this, we need to understand how the drawPicture method works. Here's the method's description in REALbasic's online help:

The first three parameters are simple: the picture object and the coordinates of where to draw that object. The parameters after that are optional. (Remember, stuff between square brackets is optional.) The simplest use of drawPicture would be drawPicture p, 0, 0. But in our case, since we want to manipulate the size of the picture, we need to use those extra parameters, which can be confusing. So let's explore exactly what they mean.

The first two extra parameters are the width and height of the destination: in other words, the picture will be resized to fit this width and height. If the destWidth and/or destHeight is smaller than the actual width and height of the picture you are drawing, the picture will be shrunk as it is drawn. If destWidth and/or destHeight are larger than the actual width and height of the picture, the picture will be enlarged. If you pass the actual size of the picture, then it's drawn at actual size (no enlargement or reduction).

Just think of these two parameters as percentages in the form: width/height * percent. So a command like this:

  
drawPicture p, 0, 0, p.width * 50%, p.height * 50%

would draw p at 50% size. Likewise,

  
drawPicture p, 0, 0, p.width * 150%, p.height * 150%

would draw p at 150%.

Of course, in reality, REALbasic doesn't understand percentages directly -- you must decimals (like .5 for 50% and 1.5 for 150%). And of course the drawPicture command won't work without the rest of the parameters on the line, so it's important you know how to use those as well.

The next parameters are a little hard to visualize at first. These are the sourceX and sourceY parameters. They represent the starting coordinates of where you'll begin drawing from. If these are zero, you'd begin drawing from the upper left corner of p. But if these are greater than zero, you'll start drawing further down and over into the picture, effectively clipping off some of the picture's left and top by not drawing it.

It's hard to explain sourceX and sourceY without also explaining sourceWidth and sourceHeight: those indicate how wide and high a chunk of p we'll grab to draw. Between the four parameters, you can grab all of p or any rectangular sub-portion of p and draw it, at any size you want (actual size, larger, or smaller).

To grab all of p, just pass 0, 0 as the sourceX and sourceY parameters, and p.width and p.height as the sourceWidth and sourceHeight parameters.

To grab a sub-portion of p, pass the left and top coordinates of the rectangular area you are wanting to grab, and the width and height of that rectangle. It's easiest to visualize this with a diagram:

This shows you exactly what we're doing with Zoomer. The faint background picture is p -- all of p. But since zoomWindow is smaller than p, we're only drawing a sub-portion of p. The coordinates of the upper left corner of what you see in zoomWindow is what we pass as the sourceX and sourceY parameters. The width and height of that sub-portion is the width and height of zoomWindow.

Calculating those upper left coordinates requires a little math. Remember, we're grabbing a rectangular sub-portion based on where the user's arrow cursor is located. The new upper left coordinates are represented by x1 and y1. The actual location of user's mouse are x and y.

As you'll remember from our last lesson, these are in local window coordinates. Because of that, we want to translate these to editField1 coordinates. We do that by subtracting the left/top of editField1 from x/y.

  
x1 = x - editField1.left
y1 = y - editField1.top

That gives us adjusted coordinates relative to editField1. If we just stopped there, Zoomer would work. In fact, try it: temporarily comment out the if-then code as shown below and try running the program:

  
// This part calculates what portion of the editField
// we're going to be drawing, based on the cursor
// position.
x1 = x - editField1.left
'if x1 < 1 then
'x1 = editField1.left
'elseif x1 + zoomWindow.width / zoomAmount > editField1.width then
'x1 = editField1.width - zoomWindow.width / zoomAmount + editField1.left
'else
'x1 = x
'end if
y1 = y - editField1.top
'if y1 < 1 then
'y1 = editField1.top
'elseif y1 + zoomWindow.height / zoomAmount > editField1.height then
'y1 = editField1.height - zoomWindow.height / zoomAmount + editField1.top
'else
'y1 = y
'end if

What happens is that the sub-portion we draw is not limited to just editField1. Zoomer now zooms the entire window, and even beyond -- showing us garbage stuff from random memory.

This certainly isn't fatal, and it is interesting, but it's not what we're wanting. Since we only want to zoom editField1, we need to adjust x1 and y1 to make sure they're within editField1's range. So all that complex-looking if-then stuff does is make sure x1 isn't less than editField1.left or editField1.width!

The elseif portion of the code:

  
elseif x1 + zoomWindow.width / zoomAmount > editField1.width then

simply gives us a value that's back to the actual (non-magnified) width. Remember, zoomWindow's width represents a zoomed width. If we compared it to editField1.width we'd be comparing apples and oranges. By adjusting it back to actual size (via division of the zoom amount), we compare the actual width amounts of the two. This ensures that we don't let zoomWindow display stuff past the right side of editField1.

(I'm just explaining the width portion here, but it's exactly the same code for the height, except we use the height values.)

The result of all this is x1 and y1 values that are within range of editField1's position on window1. With this formula, x1 will never be too far left or too far to the right, and the same with y1 never being too high or too low.

The final bit of code is the "simple" drawPicture command. Now that we've done all our calculations, we just draw the sub-portion of the picture. We start drawing p at 0, 0, the upper left corner of zoomWindow. We pass zW and zH, our zoomed (enlarged) width and height values, for the width and height parameters. All that work we did with x1 and y1 is now used since we tell drawPicture to start drawing from those coordinates. And finally, we tell it the width and height of the sub-portion of p we're drawing is the width and height of zoomWindow (meaning that zoomWindow will be entirely filled with a zoomed graphic).

  
zoomWindow.graphics.drawPicture p, 0, 0, zW, zH, x1, y1, zoomWindow.width, zoomWindow.height

That's not so bad, now that you understand it, right? It's a little confusing keep track of all those similar-looking parameters, but REALbasic 4 does help. When you click on the drawPicture text in the Code Editor, it will display a tip window which gives you a brief summary of the command and its parameters:

Once you understand how the command works, the shorthand is usually enough to refresh your memory without having to scroll through the online help for the complete description.

Whew! That was a lot of stuff, but we made it through. While this little demo doesn't do anything particularly useful, you hopefully learned some valuable techniques from the lessons. Here's an animation of Zoomer in use:

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

Next Week

We make our own painting program. So to speak.

Letters

Our first letter is from William, who asks a very common question about REALbasic.

First, thank you for your recommendations on my last question about fractions, which you posted in a previous column. Your suggestions helped in the direction of my project. However, I have a new problem, as my projects keep growing in size.

Is there such a thing as a global variable? I would like to set d as an integer and have various windows or buttons adjust the numerical value of d. While I can set d as an integer globally, it's value is not global. I have found a work around by using static text with visible false and setting it as d in all the windows and buttons, but it seems there should be an easier way. Am I overlooking something?

Just add a property (variable) to any module. A module is a collection of properties, constants, and methods. You get it via the File menu (choose "New Module"). Anything you add to a module is global to your entire program.

Think about it: if you add a property to a window, it's within that window, so it's not global. But a module isn't within anything but your project, so it's global. (It makes sense, but REAL Software needs to explain this better.)

BTW, you can refer to a window's properties via the object chain. For instance, say you've got Window1 with a boolean property called isDone. Another window or routine could refer to isDone (even though it's not global) by saying Window1.isDone. Sometimes that's helpful and better than using globals. (Remember, globals use memory the entire time your program is running.)

Next, we hear from a guy named Squid. At least, that's the name he gave in his email. (I swear I don't make this stuff up!)

Hello!

Good job on making a great column, it helped me more than a book I got that was supposedly for "beginners"! Anyway, I'm writing a program, and I can't figure out how to make a button pressed in one window, open up another window. I know it sounds really basic (no pun intended), and it was probably answered in an earlier column, but I just recently found RBU and haven't even gotten to lesson eleven.

Hi Squid!

What you want to do is easy: in the Action event of a the button, just type the name of the window you want to open, add a period, and the Show method. Like this:

 window2.show 

You can also do:

 window2.showModal 

That will make the second window open as a modal dialog (stopping all other action in your program), if it's a dialog and not a document window type.

For more on this, look through some of the RBU programs where we handle dialog boxes.


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