This is a tale of the Input Server and the devices it recognizes. I know you've heard this before, but today we're going to approach the topic from the point of view of "pointing devices" (mice, tablets, and the like), with a concentration on tablets. First, let's review:
Way back before we knew better, user input was handled by the Application Server. The user would move the mouse or type on the keyboard, and, in response to a notification from the mouse or keyboard driver, the Application Server would create a message and send it to the appropriate application. This was a closed system; the only place you could step into the input river was at the application level—well after all the important decisions had been made.
A couple of releases back, we decided to provide an "input spigot" that sat upstream of the Application Server. This new machinery, the Input Server, lets the developer create an input device add-on that listens to a particular driver (or drivers), and manufactures the messages that are then handed to the Application Server (which still delivers them to specific applications).
Currently, the Input Server recognizes two types of devices: keyboard
devices and pointing devices. When you create an input device, you have
to declare it to be one or the other. We chose the name "pointing device"
(or, in Input Server parlance, B_POINTING_DEVICE
) rather than "mouse
device" because there's more than one way to point. Tablets, for example.
However, when we move from the Input Server into the Application Server,
we see a bias towards mice. All B_POINTING_DEVICE
devices are expected to
communicate with the Application Server by creating and sending the
messages B_MOUSE_UP
, B_MOUSE_DOWN
,
and B_MOUSE_MOVED
. We chose these
messages, rather than create a new protocol, to ensure backward
compatibility. And, as with most backwardly compatible decisions, a few
modern details were left wanting. In the case of pointing devices, there
are two problems with the messages, particularly the B_MOUSE_MOVED
message:
Cursor location is expressed in view coordinates, but input devices don't know about views.
Tablets are more flexible than mice; the current B_MOUSE_MOVED
protocol doesn't express all the ways in which a tablet can "move."
To get around these problems, we've added new conventions to the
B_MOUSE_MOVED
protocol, as explained below.
The B_MOUSE_MOVED
message contains
a BPoint
"where" field that, by
convention, contains the cursor location in view coordinates. However,
views live in the Application Server, which is downstream from the input
device that has to create this message. In other words, the input device
doesn't know anything about views, and so can't translate to view
coordinates. To bridge this gap, we've modified the B_MOUSE_MOVED
convention.
When creating a B_MOUSE_MOVED
message from within an Input Server device,
you add "x" and "y" fields, setting their values to either an offset
relative to the cursor's previous position, or an absolute position in
the range 0.0 to 1.0.
Mice always use relative locations; tablets can use either, although absolute locations are the norm (more on that later).
All mice and some tablets can express the pointer location relative to its previous position. If your pointing device is operating in relative coordinate mode, you add "x" and "y" as int32 values:
int32xVal
,yVal
;event
->AddInt32
("x",xVal
);event
->AddInt32
("y",yVal
);
In this case, xVal
and yVal
are the horizontal and vertical distances, in
device-defined units, that the cursor moved. These units are interpreted
as pixels by the Application Server.
To set the field to a location, you add "x" and "y" fields as floats. For example:
floatxVal
,yVal
;event
->AddFloat
("x",xVal
);event
->AddFloat
("y",yVal
);
As mentioned above, xVal
and yVal
must be in the range 0.0 to 1.0. The
values are then scaled, by the Application Server, to the screen's
coordinate system such that (0.0, 0.0)
is the top left of the screen, and
(1.0, 1.0)
is the bottom right. By using a 0.0 to 1.0 range, the pointer
device can express the cursor location independent of screen resolution.
In both cases—i.e., relative and absolute location—the Application Server creates and adds a "where" field based on the "x" and "y" values translated to the target view's coordinate system, and throws away the original "x" and "y" fields. The fact that the "x" and "y" fields aren't retained is important, as explained below.
Tablets really feel the user. They supply a lot more information about the user's digital probing than a mouse does. Specifically, a tablet can track movement with much greater precision than a mouse, a tablet can include pressure and tilt information, and it can go into "eraser" mode.
To accommodate this information, the next release will introduce some
conventions for tablet-specific fields in the B_MOUSE_MOVED
message.
These fields, described below, won't be acted upon by the Application
Server, but they will be passed through to the application that receives
the B_MOUSE_MOVED
message. The application can decide whether it wants to
use this information or not.
As mentioned above, a tablet can encode the pointer's absolute location
through "x" and "y" fields, which the Application Server converts to a
"where" field. Although the "where" precision
(a BPoint
) is sufficient
for most applications, it's nowhere near as precise as the original "x"
and "y" values. Some applications may be interested in the "real"
tablet-precise location; but the original "x" and "y" fields are thrown
away by the Application Server.
To get around this, your tablet should copy its absolute location values into the "be:tablet_x" and "be:tablet_y" fields. So the example shown above should actually look like this:
floatxVal
,yVal
;event
->AddFloat("x",xVal
);event
->AddFloat("y",yVal
);event
->AddFloat("be:tablet x",xVal
);event
->AddFloat("be:tablet y",yVal
);
The additional fields are passed through without conversion; it's up to the application to interpret them properly.
Tablet pressure is expressed as a float in the range 0.0 to 1.0 (minimum to maximum), and added as the "be:tablet_pressure" field:
floatpressure
;event
->AddFloat
("be:tablet pressure",pressure
);
Tilt is in the range -1.0 to 1.0, where (-1.0, -1.0) tilts to the top left, (1.0, 1.0) tilts the bottom right, and (0.0, 0.0) is no tilt. The fields are "be:tablet tilt x" and "be:tablet tilt y":
floattilt_x
,tilt_y
;event
->AddFloat
("be:tablet tilt x",tilt_x
);event
->AddFloat
("be:tablet tilt y",tilt_y
);
Eraser mode is expressed as an int32 in the "be:tablet_eraser" field:
int32eraser
;event
->AddInt32
("be:tablet_eraser",eraser
);
The value is 1 when the pen is reversed (the eraser is on), and 0 otherwise. Other eraser modes may be defined in the future.
BeOS Release 4.5 didn't include any tablet drivers—we hope to fill this void in the next release. The tablet device that's included will certainly use the conventions described here.
If you're writing a tablet device, you should use the new fields
yourself. (Note that if you're writing a mouse device, you don't need to
add these fields to the messages that you create.) More importantly, if
you're writing an application that can benefit from a tablet's features,
you should look for the new fields in the B_MOUSE_MOVED
messages that you
receive.
Past articles about the Input Server by Hiroshi Lockheimer:
Be Engineering Insights: An Introduction to the Input Server
Be Engineering Insights: An Introduction to Input Method Aware Views
Software engineering is a rewarding profession and a demanding one. "Multidisciplinary" only vaguely describes the variety of knowledge and skills a good engineer should have, especially at a place like Be. However, just as important as selecting the right algorithm, the right compiler, the optimum keymappings in the editor, is the ultimate choice an engineer must make: where to eat.
We are fortunate at Be to be located within 10 minutes walk of many excellent eateries, an often-overlooked contribution to the quality of work we're able to do here. Menlo Park—hardly a downscale town—is adjacent to upscale Palo Alto and Atherton, which affords us a broad spectrum of cuisine, from no-star fast food to four-star slow food.
Asian food seems to be the most popular in the Be lunchroom. Su Hong Chinese Restaurant (known as "The Hong") gets a lot of Be business. Our Fearless Leader JLG is often seen there ordering the Menlo Combo to go. If Su Hong is too clean for you, there's always Mr. Chau's, a bit further away. We have no less than *three* choices for sushi: Akasaka, Gombei, and Tokyo Subway ("Sokyo Tubway"), the last being the popular choice.
Other ethnicities are also well represented. Just up the road is Gaylord's Indian restaurant. For Mexican food you have your choice of burritos: MexToGo gets frequent visits, as does Una Mas, and 360° Burrito (where they go by their yuppie name, "wraps"). If you hanker for Swiss cuisine there's Amandine, where waiters maintain neutrality in not favoring one dish over another. For French food there's Le Pot Au Feu, though I don't see many Be regulars there. I enjoy Italian, at Florentines, Trattoria Buon Gusto, and Scala Mia, places that understand basil. (I love basil. Basil should be used in everything. Breast of chicken stuffed with sun-dried tomatoes and basil, dolmas wrapped in basil instead of grape leaves, basil bread, basil salad, basil sandwiches, basil ice cream, Basil Rathbone...the list goes on.)
Pizza, the long-standing basic food group for engineers, is available at Round Table, but if you're here in the evening, Applewood, open only for dinner, is a superior venue.
Deli-style sandwiches? No problem. Togos just opened next door, and Jan's is just on the other side of the tracks. But if you like great heaping eye-watering gobs of Dijon mustard, Le Boulanger ("Boolanger") serves up fancy sandwiches on fresh-baked bread, although some may prefer the less pretentious fare at The Oasis. All things chicken are found at the oddly named Koo-Koo-Roo ("The Koo").
Looking for a gimmicky restaurant with deep-fried everything? There's a Chili's right next door. If you prefer more "traditional" unhealthy food, like burgers, there's the ubiquitous McDonald's®™©(BFD), or you can walk a bit further and get a real burger at Clark's. There's an Arby's across the street, a place so amazingly cheesy that their Web page is hosted on AOL. Fishier fare is just next door at Cook's Seafood, and coming soon is 3 Fish, under construction next to our building.
Looking for healthier cuisine? That's covered, too. Late For The Train is so healthful, you won't recognize it (it's organic, don'cha know). Hobee's in Palo Alto (my favorite place) is less austere, and they serve killer breakfasts all day.
If your goal isn't so much eating as hanging out and relaxing, then Caffe Borrone ("Cafe Baloney") may be your spot, conveniently located next to a Ricochet repeater for your laptop. Or you could hike up to the bistro above Draeger's supermarket, where you can order >from one of about fifteen teas and several dishes garnished with goat cheese.
If, like us, you just IPO'd (and if you haven't, what's the matter with you?), then you'll probably want to celebrate by visiting a fake-teak-and-crystal upscale place like Left Bank which opened a few months ago, or the even more recent Wild Hare, which actually serves hare and other game meats (their bison burger is very good). Or you could go to the long-established Dal Baffo, a place whose picture you see in the dictionary next to the term "exquisite dining."
If you need to get some shopping done over lunch, there are lots of places in Stanford Shopping Center, ranging from the mega-salad bar at Fresh Choice to Max's Opera Cafe, where they will sing Happy Birthday™© to you in twelve-part harmony.
I'm sure after this article goes to press that I'll start hearing from everyone in the office about places I neglected to mention, or places I portrayed in an insufficiently flattering light. Such a response, I think, would confirm that we at Be take our food as seriously as our products. It's said that programmers are engines that turn pizza into code, but the quality of the code you get is only as good as the fuel you put in. So we're fortunate to be located where we are, with a full spectrum of dining options available. Though other factors—like a flair for programming—are just as important to the success of our company, I'm sure Napoleon would have agreed that, like an army, a software company travels on its stomach.
(Trademarks used above are the property of their respective owners. The mention or failure to mention a particular establishment should not be interpreted as an official endorsement or nonendorsement of said establishment by Be, Inc. The author cheerfully accepts bribes in exchange for mention in future articles.)
Digital images are easy to create. Putting pixels into memory is a simple, well-understood art. Writing them to disk in a useful manner is a trifle more complex, but the BeOS Translation Kit provides a number of tools to help manage that complexity. All in all, putting still images on disk isn't something that causes headaches.
Digital movies are another story. As with still image files, there are a number of different file formats to choose from, but there are also choices of compression scheme, whether and how to provide a sound track, and so on. Creating animations means a lot of supporting code, especially if that support is duplicated in a number of different applications.
BeOS R4.5 introduced some new Media Kit classes designed to provide that
support to all applications: BMediaTrack
and
BMediaFile
. A BMediaTrack
represents one coherent stream of data, for example a digital sound track
or a sequence of images to be played at a specified rate. A BMediaFile
contains one or more BMediaTrack
s, and represents the movie as a whole,
providing the interface that determines the file format as well as other
attributes of the movie file as a whole.
How do these two classes help you create digital movies? As I mentioned,
still images are relatively easy to create. BMediaTrack
provides an
interface for putting a sequence of still images together, and
BMediaFiles write BMediaTrack
s out to actual disk files in the proper
manner. The Media Kit has functions for iterating over the list of all
installed media file writers as well as the list of supported codecs for
a given media format. All you need now is a simple way of hooking them
all together.
That brings us to this week's sample class—BitmapMovie
. This class
provides a very simple interface for creating a movie file out of a
sequence of BBitmap
s. Before I describe it, I should point out that you
can find the source code for BitmapWriter
at this URL:
<ftp://ftp.be.com/pub/samples/media kit/BitmapWriter.zip>
When you construct a BitmapMovie
object, you specify its dimensions and
the color space that your BBitmap
s will use (generally you'll use
B_RGB32
, since that's the most widely accepted color space on the BeOS).
Then choose the file format, media format, and codec you want, and call
BitmapMovie::CreateFile()
. That creates the file on disk according to
your specifications. Now all you need to do is call
BitmapMovie::WriteFrame()
repeatedly, passing in the BBitmap
s
representing successive frames of video, until you're finished. At that
point you call BitmapMovie::CloseFile()
, and your movie is safely
ensconced on the disk.
The BitmapWriter
sample program demonstrates
the BitmapMovie
class by
creating a simple animation and writing it to disk. The application also
demonstrates the mechanism for determining the available file formats and
codecs, and presents a user interface for selecting any of the supported
combinations. The code is pretty simple; I'll just go over a few
highlights and important points to remember when you write your own code
to handle BMediaFiles and BMediaTrack
s.
The basic process for creating movie files is this:
Create the BMediaFile
object.
Create the BMediaTrack
objects that reside in the file.
Optionally adjust the codec's image quality or other settings.
Commit the file's header to disk.
Write data to the tracks.
Close the file.
BMediaFile::CreateTrack()
takes the *input* media format as an
argument. That is, you specify the format of the buffers you'll be
providing to BMediaTrack::WriteFrames()
, not the format of encoded data
that you want on disk. The codec argument is what determines the
encoding.
Deleting a BMediaFile
also deletes any
BMediaTrack
s associated with
that file.
Don't call BMediaFile::CloseFile()
more than once. Sad to say,
there's a bug in at least one media file writer shipped with R4.5 that
causes crashes in this case. Just don't do it.
Specify the quality of encoding to use before calling
BMediaFile::CommitHeader()
. In general, codecs can't change quality
settings on the fly, and the codec parameters might be part of the
file's header information.
Attaching a BView
to a BBitmap
lets you use the standard BeOS drawing
API to generate the frames of your movie. This is a great technique for
producing off screen images without having to do the pixel pushing
yourself.
There is currently no way to determine all the codecs installed in the system; you can only tell which codecs are available within a given file format and which support a given media format. For example, if you change the sample code to use a 720x480 movie size, you'll notice that the DV codec is available; this is because the DV codec only supports that size.
Last week's installment of Rephrase showed you how to open large files.
Now you need an easy way to view the file's contents. Although BTextView
provides keyboard navigation (PageUp and PageDown), scroll bars and a
resizable window are more useful, so we'll add them this week.
You'll find this version of Rephrase at:
<ftp://ftp.be.com/pub/samples/tutorials/rephrase/rephrase0.1d3.zip>
Rephrase 0.1d3
New Features
Resizable Windows
Scroll Bars
"Intelligent" Window Sizing
A frame rectangle is the size and position of a window or a view in its parent's coordinate space. For a window this is the screen; for a view it is its window or parent view. It is the rect where the object is responsible for displaying itself.
When the frame rectangle of a window or a view changes, a frame event
message is sent. Windows and views that have the B_FRAME_EVENTS
flag
receive these messages and the FrameResized()
or FrameMoved()
hook
functions are called. The functions themselves are straightforward:
FrameResized()
reports the new width and height of the rectangle;
FrameMoved()
informs the object of its new position in its parent's
coordinates (the screen for a window or the view's parent).
Currently Rephrase doesn't care
about FrameMoved()
calls. FrameResized()
calls are another matter. The phrase display window responds to
FrameResized()
calls to adjust the size of the text view's text
rectangle. The BTextView
also cares about
FrameResized()
messages; it
uses them to update any scroll bars that might be targeting the view.
Scrolling a view changes the portion of its contents visible in a view's
frame. BScrollBar
s are BView
s
that visually represent the section of the
display view shown and provide a means to scroll that view. BView
s and
BScrollBar
s interact to keep in sync:
BScrollBar
s call BView::ScrollTo()
and BView::ScrollBy()
, and
BView
s call BScrollBar::SetValue()
.
BTextView
takes this interaction a bit further. When the content in a
text view changes, it will update the value, proportions, and ranges of
any BScrollBar
targeting it. All the work is
done by the BTextView
; just
create the BScrollBar
s and target the text view.
pDisplay
now has a
BTextView
* member,
fEdit
. This is used throughout
the window to access the text view.
[pDisplay.h
:20]
pDisplay
now creates its BWindow
superclass with a document window
look and removes previous restrictions on zooming and resizing.
[pDisplay.cpp
:27-28]
BuildMenus()
is a new function that encapsulates building the main
menu bar. It was introduced mainly for readability purposes.
[pDisplay.cpp
:209-232]
pDisplay::FrameResized()
adjusts the size of the text rectangle in
the text view. It remains a constant 4 pixels inset from the text view
proper. The text flickers a bit, as the entire text view may need to be
redrawn because of differing line wrap.
[pDisplay.cpp
:154-165]
Window size is determined dynamically, based on the default font and
the current printer settings, rather than having a fixed rectangle
size. The window's text rectangle is set to be 40 lines high, based on
the font. The width of the text rect is either the width of the current
printer's printable rectangle or 45 ems. An em is a typographical unit
based on the width of an uppercase M. For roman character fonts it's
considered a good estimate of the widest character in the font.
[pDisplay.cpp
: 31-82]
The BPrintJob
class is used for interacting with the print server.
We'll use it again in a future installment addressing printing issues.
In this issue we use BPrintJob::PrintableRect()
to return a BRect
that
represents the default printer's rectangle for printing. If a printer
has been set up, Rephrase uses its printable rect's width for the width
of the pDisplay
text rectangle. If no printer has been set up, the
rectangle will be invalid.
[pDisplay.cpp
:43-53]
The BFont
class represents a font used to display text for a view.
You can use the class to acquire information about the font. Three
global font pointers are defined for the system: be_plain_font
,
be_bold_font
, and be_fixed_font
.
You make these systemwide settings in the Font
preferences panel. be_plain_font
is the default font for all text views.
Rephrase uses BFont::GetHeight()
to determine the height of a line of
text. BFont::StringWidth()
determines the size of an em (see number 5
above). This value is used to set the window's minimum width, and
perhaps the default width if no printer is available.
[pDisplay.cpp
:35-41]
Window size limits are based on font and screen size. The minimum
size is based on a text rect 12 ems wide and 10 lines deep. The maximum
is the size of the screen.
[pDisplay.cpp
:74-82]
To enlarge a BRect
by a specific amount on each side, pass negative
values into BRect::InsetBy()
. Be sure to remember to offset the
rectangle to B_ORIGIN
(
)
if you're expecting the rectangle to start there.
[BPoint
(0,0)pDisplay.cpp
:58-59]
Next week: BFilePanel
s and saving files.
There are things I shouldn't say, at least not if I'm unprepared to see a throwaway remark bounce back and become a reality. I used to say, "Don't ask me 'When is our IPO?' My office overlooks the parking lot, and when I see the BMWs of investment bankers fighting for spaces, I'll know it's time."
Early this year, one of our friends in the banking community came to see us and flatly told us to get moving, to start preparing for an Initial Public Offering. "But, but, but," I stuttered. "I may have been abducted by aliens and raised in California by VCs, but I'm still a French Farmer at heart—too financially conservative to have debt or day-trading account. It's too soon, we don't have the revenues and earnings record to qualify for an IPO."
They said I should be ashamed of myself. How could I, a professed born-again capitalist, let the statist roots of my culture of birth corrupt my thinking? In other words, who was I to second guess the market?
Just as the simple and memorable catechism of the Universal Life Church states, "Only You Can Decide What The Good Is For You," only the market can decide what a good investment is. And the market's calculus of risk and reward has changed since the days of Standard Oil, General Motors, and General Electric. At each cycle of the market's evolution, there have been good and bad investments, fairly balanced between old school conservative companies and the New Age ones of the time. Not everyone felt that RCA or IBM was a good investment, or that they signalled a new era. And the same was true for Intel, Dell, or Microsoft.
Right now, the good banker said, the market has decided that "The Good" is in betting on the future of the Internet. You can do a Greenspan impersonation with a French accent and question the exuberance, but even the Fed Chairman now concedes that there are potential huge winners, future GMs, GEs, and Standard Oils in cyberspace.
I've seen what BeOS does, the good banker continued. With the huge investments in broadband, fat pipes to homes and offices everywhere, you have a role to play on both ends of these pipes, in the creation and consumption of digital media. It's hard to argue with a banker who's selling you your own product.
That's how we got started on the road to the IPO. And we found ourselves being sold our own ideas again when we began interviewing investment banking firms. By February of this year, the banking community was preparing what has become the most active IPO season ever, the Spring of 1999. It showed in the attention span of some, but the quality of discussions was generally pretty high, with a good consensus on the risk/reward proposition to present to investors.
In the end, I was impressed by the quality, the professionalism, and reaction time of the best presentations. In the case of the two that drove our choice, within 24 hours, the banking firm came back with a detailed analysis of our business, its place in the landscape, its competition, its risks and advantages. I know it's their business to do this, I know they have PowerPoint and Excel templates, but faking intelligence in person is harder.
With all due respect to the bankers (I'll come to their part later), it was the analysts who did it. In the case of Volpe Brown Whelan, the lead firm, it was Charlie Finnie (called Mr. Charles Finnie by the veddy British New Economist) and his encyclopedic knowledge of the world of digital media and the Internet. In the case of Needham, our New York-based firm, it was Brent Williams and his very assertive and articulate command of the world of operating systems and multimedia. Charlie and Brent sang our song better than we ever did—and under their tutelage we would learn how to best present ourselves to the investment community.
But before we got to the pitch, we had to meet the SEC's requirements -- that is, we had to file a prospectus and get clearance to proceed. For that phase of the process, see Going Public: Part II, next week.