DynaDraw, Part Two

In the previous article on DynaDraw, we built a no-frills app -- a basic window 
and a view, and our drawing algorithm.  In this article, we get to the fun part --
adding lots of controls which modify how the calligraphy strokes are drawn.

Download the code from: ftp://ftp.be.com/pub/samples/r3/interface_kit/dynadraw2.zip, 
compile it, and see how the new features work before getting into the details
of this article.  I especially dig wireframe mode, which calls StrokePolygon instead
of FillPolygon.  

Let's start out the way we did last time, looking at what we'd like to do, and 
what we'll need to do it:

First, we'd like a menu bar across the top of the window.  Under it, we'll put
two menus: "File" and "Controls".  Under File, we'll have the items "About", which
brings up an About Box, and Quit, which, well, quits.  Under "Controls", we'll have
three items: "Tweakables", which brings up a panel for controlling the mass, drag,
and other parameters; "Color", which allows us to change the color of the pen;
and "Clear Screen", which clears our drawing.

The Tweakables panel and the Color panel will each have their own windows.
R3 introduces a new window type, B_FLOATING_WINDOW, which is perfect
for control windows.  Floating windows have smaller borders and tabs than 
regular windows, and they are active whenever the application is active.

The next question is: which object should be responsible for managing these 
control windows?  It's possible to add these duties to the DDWindow class, but
if our application grows to include more windows, this approach will be 
cumbersome.  A better solution is to subclass BApplication in an object which
manages the creation and destruction of the control windows.

Here's our object list now:

	- FilterView, from the first article,
	- DDWindow, from the first article,
	-TweakWin, which controls pen parameters,
	- ColorWin, which controls pen color, and 
	- DDApp, which manages the application and windows.


Messaging

Good object layout is half the battle, and the other half is good messaging.
The heart of this entire application lies in the MessageReceived() functions
in each of the classes listed above.  Reading those functions will tell you 
what duties those objects perform, so let's go over them now:

Our application object handles B_ABOUT_REQUESTED, and B_QUIT_REQUESTED,
which is standard.  It also handles TWEAK_REQ and COLOR_REQ, which are
requests to open the tweakables window and the color window.  These messages
(or more properly, commands, because they are messages which contain no data)
come from DDWindow, which sends them in response to the user selecting the
appropriate menu item.  Our application object also handles TWEAK_QUIT and
COLOR_QUIT, messages sent from the tweakables window and from the color
window the user has closed them.

ColorWin has only one specific message to deal with: COLOR_CHG, which is
a message we requested the BColorControl send us when the user has chosen
a new color.  (This is specified in the BColorControl constructor.)  In this case,
we want to alert the DDWindow of the new color selection, so we pack the
RGB value of the user's selection into the message:

	 case COLOR_CHG:
		/* get the current color settings, and attach to the message */
		/* and pass it along to the handler */
		rgb_color clr = cc->ValueAsColor();
		swatch->SetViewColor(clr);
		swatch->Invalidate();
	 	msg->AddInt16("red", clr.red);
		msg->AddInt16("green", clr.green);
		msg->AddInt16("blue", clr.blue);	
		handler->PostMessage(msg);
		break;

So our message is really getting double-duty: it serves as a command from the
BColorControl object to ColorWin indicating a color change, and it serves as a
message (with the RGB value as data) from ColorWin to DDWindow.

TweakWin's MessageReceived() is more of the same.  It handles MASS_CHG,
DRAG_CHG, WIDTH_CHG, and SLEEPAGE_CHG, which arrive from the BSliders
created in TweakWin's constructor.  Again, an int32 value is added to each of
these messages, and the message is then sent to the DDWindow object.

TweakWin also generates FILL_CHG and ANGLE_CHG messages, from the 
Fill and Angle check boxes.  We don't need to add any data to these messages,
so we can send them directly on to the target, DDWindow, rather than having
TweakWin::MessageReceived() pass the message along to DDWindow:   

	fill = new BCheckBox(BRect(25,320,135,340), "fill", "Wireframe",
						 new BMessage(FILL_CHG));
	fill->SetTarget(handler);	  /* send FILL_CHG directly to handler */

SetTarget() is a function defined in BInvoker, which is a subclass of BCheckBox.

This technique is also used in the menus of DDWindow.  Since we want 
B_ABOUT_REQUESTED and B_QUIT_REQUESTED to be sent along to the 
application object, we can use a shortcut function, SetTargetForItems(), which
will send the messages of all menu items in the File menu to be_app:

	BMenu* menu = new BMenu("File");
	menu->AddItem(new BMenuItem("About", new BMessage(B_ABOUT_REQUESTED)));
	menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));

	/* Both the About and Quit messages should be directed to be_app */
	menu->SetTargetForItems(be_app);
	mb->AddItem(menu);

In the Controls menu, although we want TWEAK_REQ and COLOR_REQ to go
to be_app, we want CLEAR_SCREEN to post to DDWindow, so we specify
the targets one at a time:

	menu = new BMenu("Controls");
	BMenuItem* tmpItem = new BMenuItem("Tweakables",new BMessage(TWEAK_REQ));
	tmpItem->SetTarget(be_app);
	menu->AddItem(tmpItem);
	
	tmpItem = new BMenuItem("Color", new BMessage(COLOR_REQ));
	tmpItem->SetTarget(be_app);
	menu->AddItem(tmpItem);
	
	menu->AddSeparatorItem();
	menu->AddItem(new BMenuItem("Clear Screen", new BMessage(CLEAR_SCREEN)));
	/* Target not specified for Clear, so the target will be DDWindow */

	mb->AddItem(menu);
	AddChild(mb);

Notice that in DDWindow::MessageReceived(), we simply pass all of the messages
from the tweakables window and the color window to the FilterView, which actually
interprets them.  Note how values are extracted from messages in FilterView's
MessageReceived():

	 case COLOR_CHG:
		int16 red, green, blue;
		msg->FindInt16("red", &red);
		msg->FindInt16("green", &green);
		msg->FindInt16("blue", &blue);
		SetHighColor(red,green,blue);
	 	break;


BSliders

R3 introduces the BSlider class, an incredibly useful control device.  It's really
flexible too, allowing you to customize the look of the thumb, the tick marks, 
the track, and more.  Since BSlider only deals with integers, so you may need
to do some small tricks to get the value you want, such as setting up a slider
with a range of values between 0 and 100, and using this as a percentage of
your maximum value.  This is what we're doing with Mass, Drag, and Width.

Also, BSliders are set up to have the left hand side be the minimum value, and 
the right hand side as the maximum value.  Sometimes this isn't what you
want to do: in the case of "Sleepage", it seemed more natural to me to "sleep less"
towards the right of the control, and "sleep more" towards the left.  In this case,
just set the slider up to go from your minimum to your maximum, and when reading
that value, subtract it from your maximum, and add that value to your minimum.  
In the sleep case, I set up the slider to go from 3000 microseconds to 30000 
microseconds, and then (in FilterView::MessageReceived) I subtract the slider
value from 30000 (the maximum), and add the difference to 3000 (the minimum).


Future Enhancements

This program can be extended in a variety of ways, and I encourage you to do so.
Some small projects might include letting the user control the background color of
the FilterView, and dynamic updating of the color swatch.  Larger projects include
saving the drawing to disk, printing, and letting the user undo brush strokes.  
Experiment and enjoy!



