It's 4:00 a.m. and my plane leaves for Germany in three and a half hours. Soon I'll be guzzling hearty German lager, squeezing hearty German damen, and eating hearty German bockwurst. But first, I have to write this article.
But what to write about? Sometimes it's easy to find a topic, like when we have a release right around the corner. In those magical times, engineers float through the halls of Be, on waves of pure platform viability. We compete in naked hacking tournaments for the honor of being the next to write a Newsletter article about all the cool stuff we're giving you in Just A Few Days.
This is not one of those times.
Now, don't get me wrong. There's a lot to talk about already in the coming attractions for R4. But as our fearless leader would say, we wouldn't want that to come prematurely.
So, I was stuck, and had plane to catch—soon. It all seemed hopeless until Pierre suggested I dig around in my copious archives of Stuff for some sample code, and cheat by using that.
And, hey! I found a little program I'd been using to test scaled bitmap drawing while I was rewriting that code. I decided that if I spent an hour or so prettying it up, it would serve as sample code and you guys probably wouldn't even be able to tell I was copping out. So this is a little app which I have dubbed, with utmost originality and a nod to our friends at Apple, QuickPaint:
ftp://ftp.be.com/pub/samples/graphics/obsolete/QuickPaint.zip
Let me warn you: QuickPaint is not long on features, but it has great simplicity going for it. I think it's a good example of how much a Be app can do with very little code, and still be well-architected and extensible. The line count, including headers, is a little over a thousand.
What's interesting about it? For one thing, it demonstrates a simple yet extensible document/view architecture using BMessages. It allows you to open any number of windows onto a single bitmap "document," make modifications in any of those windows, and have all the views of that document be updated in real-time.
The idea is simple: the document keeps a list of editors, and notifies them (via BMessages) any time the bitmap changes, with data about what has changed. The editors receive the messages and update their displays. Each editor can display the data any way it wants; in this case, each editor has its own scaling.
To try this out, launch QuickPaint and select "Open new view" from the View menu. Do this as many times as you want. Put them all over the screen, and use Alt/Command-(plus) and Alt/Command-(minus) to get different scaling levels. Then chose a color and a tool from the ToolBox window and draw in one of the windows. Your interaction is scaled to match the pixels in the window you are editing in, and all the other views are updated immediately.
Other than that, you'll notice that each tool is a BHandler and the ToolBox maintains a BLooper which multiplexes between them, depending on the current tool selection. The current tool is simply the PreferredHandler of the BLooper.
Finally, there is read-only/read-write locking done on the bitmap, to make sure that nobody is blitting the bitmap while a tool is writing in it. This is necessary because messages can arrive at editors at any time, and we don't want bad updates happening. However, making it a read-only/read-write lock (i.e., many readers, one writer) means that many editors can handle updates simultaneously. Only the drawing tools need exclusive access to the bitmap.
Some exercises for the reader, for those of you who feel like playing with the code:
Implement the Open/Save/Save As menu options. This should be very simple using the Translation Kit, and would add very little code.
Make the tools plug-ins, using add-ons.
Implement a Bezier tool.
Implement a text tool.
Right now, the tools get confused if the window moves or changes scaling while you're in the middle of using one. Make them handle this case correctly.
Ship it!
Happy coding, and don't worry...I'll let you know all the dirt in my next article.
This week we have a simple introduction into a topic that most BeOS users and developers will find familiar: the query. Even if you've never looked at the programming behind the scenes, most everyone has used the Tracker's menu item to do a search of their hard drive. The file system query is what's behind this search.
A query asks the file system for a list of entries that satisfy certain criteria. These include standard file system information (such as name, size and modification date of a given file) and file system attributes. A query must contain either one of the standard file system information fields, or an attribute that has been indexed.
If you need more information on attributes and attribute indexing, please look at the following Newsletter articles:
Developers' Workshop: addattr, catattr, listattr, rmattr....
Developers' Workshop: Of Indexes And entry_refs
Developers' Workshop: Proper Attribution
A static query is a one-time snapshot of files on a disk that met the specified criteria at the time the query was made. Depending on other activity on the machine, the list of matching items can change by the time you receive it, and you must be prepared in case that happens. Next week we'll delve into the more complicated issue of live queries and node monitoring to make sure your list remains up to date. This week's article provides the foundation for that even more interesting discussion.
Let's examine the make-up of QueryApp, the sample code provided with this article. It can be found at:
ftp://ftp.be.com/pub/samples/storage_kit/QueryApp.zip
QueryApp is very straightforward, and somewhat limited. All the
interesting work is done by the QueryWindow
class. This provides an
interface that allows the developer to search the drive the application
is on for folders, source code, or files (non-folders) that have a
certain case-sensitive string in their names. The query string that was
built is displayed, along with the paths of matching entries.
The BeOS has two interfaces for creating static queries: a straight C
function-based interface, and the more common (and flexible) BQuery
class. QueryApp demonstrates both
methods, and allows the user to choose
the underlying method (to demonstrate that both provide identical
results).
Let's begin by looking at the implementation from FetchC()
:
voidQueryWindow
::FetchC
() { //gather the information from the views charquery_string
[1024]; if (fFileType
==FILES_TYPE
) {sprintf
(query_string
, "((BEOS:TYPE!="application/x-vnd.Be-directory")" "&&(name=="*%s*"))",fNameString
->Text
()); } else if (fFileType
==FOLDERS_TYPE
) {sprintf
(query_string
, "((BEOS:TYPE=="application/x-vnd.Be-directory")" "&&(name=="*%s*"))",fNameString
->Text
()); } else if (fFileType
==SOURCE_TYPE
) {sprintf
(query_string, "((BEOS:TYPE=="text/x-source-code")" "&&(name=="*%s*"))",fNameString
->Text
()); }fQueryString
->SetText
(query_string
); //find the device id of our application app_infoinfo
;be_app
->GetAppInfo
(&info
); //info.ref.device is our device id DIR *query
; dirent *match
;query
=fs_open_query
(info
.ref
.device
,query_string
, 0); if (query
) { while(match
=fs_read_query
(query
)) { //create an entry ref from the dirent entry_refref
;ref
.device
=match
->d_pdev
;ref
.directory
=match
->d_pino
;ref
.set_name
(match
->d_name
);BEntry
entry
(&ref
); if (entry
.InitCheck
() ==B_OK
) {BPath
path
;entry
.GetPath
(&path
); if (path
.InitCheck
() ==B_OK
) {BStringItem
*item
= newBStringItem
(path
.Path
());fItemList
->AddItem
(item
); } } }fs_close_query
(query
); } }
As you can see, the C interface to queries is simple. A query token is
returned to you through a call to fs_open_query()
. You specify the volume
you want to search, and a query string that defines the search
parameters. Then you retrieve successive dirent pointers from the query
using fs_read_query()
. This represents an entry that matches the criteria.
Next you use this dirent to create an entry_ref, which you use to go through the standard progression to the path that you want. This path is then added to the list of paths. Note that we are careful checking the status of our entry. It's possible that by the time the entry is created, the resource doesn't exist there anymore.
While this is not as important in this case, where we simply want the
path, it would be very important to catch a failure in the transformation
process if the actual data of the entry needed to be accessed (say
through a BFile
or a BNodeInfo
).
When fs_read_query()
returns NULL
, there
are no more matches or an error has taken place. We break from our loop
and make sure to close the query with fs_close_query()
.
The C++ interface through the BQuery
class is a little more user
friendly. For starters, the class gives you two ways to define the query,
through a text string like the C Interface (using BQuery::SetPredicate()
)
and through a postfix (Reverse Polish Notation) stack of operations. Take
a look at the FetchQuery()
function to see its use:
voidQueryWindow
::FetchQuery
() {BQuery
fQuery
; //gather all of the information from the views char *type
=NULL
;fQuery
.PushAttr
("BEOS:TYPE"); if (fFileType
==SOURCE_TYPE
) type = "text/x-source-code"; elsetype
= "application/x-vnd.Be-directory";fQuery
.PushString
(type
); if (fFileType
==FILES_TYPE
)fQuery
.PushOp
(B_NE
); elsefQuery
.PushOp
(B_EQ
);fQuery
.PushAttr
("name");fQuery
.PushString
(fNameString
->Text
());fQuery
.PushOp
(B_CONTAINS
);fQuery
.PushOp
(B_AND
); size_tlen
=fQuery
.PredicateLength
(); char *string
= (char *) malloc(len
+ 1);fQuery
.GetPredicate
(string
,len
+ 1);fQueryString
->SetText
(string
); free(string
); /* use this application's volume */ app_info info;be_app
->GetAppInfo
(&info
); //use the ref of the application binary //use the device from this ref to create a volumeBVolume
vol
(info
.ref
.device
);fQuery
.SetVolume
(&vol
); //fetch the queryfQuery
.Fetch
(); //Iterate through the entriesBEntry
entry
;BPath
path
; while(fQuery
.GetNextEntry
(&entry
) !=B_ENTRY_NOT_FOUND
) { if (entry
.InitCheck
() ==B_OK
) {entry
.GetPath
(&path
); if (path
.InitCheck
() ==B_OK
) {BStringItem
*item
= newBStringItem
(path
.Path
());fItemList
->AddItem
(item
); } } } }
The postfix notation lets you push operations on to the stack. You first
specify the attribute you want to push with PushAttr()
. Next you define
the criteria you want to compare it against with the various PushType
functions (PushString()
, PushInt32()
,
PushFloat()
, and so on. Then you
define how to compare or combine those definitions with the PushOp()
function. The Be Book describes this process in detail:
BQuery
s also are more flexible in how they present
matching entries to you. The C interface only allows the use of the
dirent structure (through
FindNextDirent()
). This is fast, but requires some
work before you get reasonably useful objects. Like
BDirectory
objects, BQuery
s
also let you get ready made entry_refs or BEntry objects. In this case we
ask for the BEntry
object we built in the C
functions, and after taking appropriate safety precautions, retrieve and
store each matching path.
The final bit of flexibility provided by the BQuery
class is the ability
to create live queries. As mentioned earlier, live queries add additional
complexity to the query process. For that reason I'll continue next week
with a detailed look at keeping a query list in sync with an
ever-changing file system.
CompuServe is a wonderful worldwide local ISP—no contradiction in terms. Just one local call in Tokyo, or Lausanne, or Paris, and I'm on the Net. Yesterday—after a week's vacation (unwired) in Alsace—I dialled up from Paris. Responsibly, before reading Ann Killion's excellent World Cup columns on the Mercury Center, I checked Be's home page and noted an energetic call to geeks of all backgrounds to come and join Be. (The home page has since changed; for employment opportunities, see http://www.be.com/aboutbe/jobs/index.html.)
This gives me the opportunity to comment on our recruitment philosophy. We serve four main constituencies: BeOS applications developers; customers who use BeOS applications; business partners—such as OEMs and distributors—who use the BeOS and its applications to add value to their own product and services; and shareholders who provide the company with financial fuel and expect rewards commensurate with the degree of risk they take. Shareholders include Be-ers who invest their time, energy, skills, and experience in the venture.
Do individuals who join the company share common traits? Yes and no.
Let's skip quickly over the obvious. Different tasks require different personality traits, different interests and experience. More relevant, we like and need diversity. Not for reasons of political correctness (see HR, below) but for effectiveness. A microcosm of diversity inside the company enhances our effectiveness in connecting to diverse constituencies.
Easier said than done. Scanning cultural backgrounds, we're a mini-UN, representing countries from Croatia to Africa, China, Japan, UK, Serbia, Belgium, France, Norway, Switzerland, Germany, Russia, Ukraine, Italy, Sweden, Czech Republic, and a few others I may be forgetting. Our associates come from an interesting range of horizons, as befits the very meritocratic Silicon Valley.
On gender, I'm not so sure—literally. It goes without saying that we are an equal opportunity employer. Several women, such as Valerie Peyre and Sylvie Pelaprat, have been promoted to positions of responsibility. However, there's no denying that women are a numeric minority at Be.
How proactive we should be in filling more engineering, marketing, and senior management positions with women is an open question for us and our (female) recruiter Laurie Gamelsky. One of our directors, Heidi Roizen is also very interested and proactive in this regard, and thinks the industry in general excuses itself too easily by using statistics and assuming there aren't enough female candidates.
Diversity aside, we have common goals and methods, and are looking for related traits. In the first place, as we like to stay light on management layers, we look for self-directed missiles. Once goals are agreed, we prefer individuals who require less than the average amount of guidance. Second, we like individuals who can keep the business as a whole in perspective, regardless of their individual job description.
In other words, writing code or a Newsletter column or supporting developers must always serve one or more of our constituencies. If the activity doesn't do that, why undertake it? This isn't as obvious as it sounds. What does or doesn't serve a constituency can lead to interesting arguments. Which gives me a good transition to literacy.
Over time, we've found that the ability to express one's ideas well in writing tends to correspond to a good fit with the organization, to the ability to stay focused on our business goals. This may sound trite and vague, a bit like the ideal employee being the proverbial 25-year-old blond Japanese with 15 years' experience, but it's nonetheless proven to be valid in practice.
Let me add that we have no HR department and don't plan to have one. We believe management should manage without interference or excuse. We love smooth and timely paperwork, 401K plans and dental coverage, which we consider the province of personnel. HR activities, if and when required, are the work of consultants who depart when they or we are done, leaving behind only memories and a bill, but no self-perpetuating infrastructure.
Last but not least, we're looking for people who understand the high-risk, high-reward nature of our work: building an OS and, more importantly, an ecosystem around it to serve our key constituencies.
BeDevTalk is an unmonitored discussion group in which technical information is shared by Be developers and interested parties. In this column, we summarize some of the active threads, listed by their subject lines as they appear, verbatim, in the mail.
To subscribe to BeDevTalk, visit the mailing list page on our web site: http://www.be.com/aboutbe/mailinglists.html.
Is it possible to make a BWindow
a "child"
of another BWindow
? Adrian
Ziemkowski sites Photoshop as a precedent:
“...their document windows are encapsulated by a psuedo workspace window. It works just like a screen where you can drag a window partially out of view...”
Some folks were not half amused by the window-in-a-window feature, but
suggested that it could be done with BView
s posing as windows. Right?
Nope—the opposition argued that the overlapping sibling prohibition
would thwart the effort. And speaking of overlapping siblings, why (it
was asked) is there this prohibition? Geoffrey Clements estimated with
some accuracy that it's much more efficient not to have to maintain
sibling order. But is the increased performance significant enough to
offset an obviously beneficial feature?
Grass roots movement, started by Luc Andre, to institute third-party library naming conventions. Mr. Andre offered that the name should at least include version and platform info; should it encode anything else? Anybody have any good naming convention suggestions?
Marco Nelissen objected that Be already offers version info through the FileTypes database. But for some, this isn't enough. They want the version to be immediately apparent. "Get Info" (Alt-I) was Tyler Riti's response. Mr. Riti also suggested that the loader present a friendlier can't-run-on-this-lib message than "X is not an executable". Methods for managing library versions, and recognizing (and possibly correcting) incompatibility were thrown about:
Smart libraries that know how to download new version of themselves.
Single-entry-point libraries where all communication is done through a single universal function name, so the library and the app will always be able to negotiate despite incompatibility.
Take advanage of the library path: Install app-specific library versions in the app's "lib" directory.
What's the cleanest way to get out of app land? Gerardo Diaz Cuellar mentioned that he was getting an occasional main() crash on the way out the door. “Do I have to free anything special. Or do anything special. Perhaps in QuitRequested?”
The context of Mr. Cuellar's crash indicates problems in the heap—a thorny problem since the app doesn't necessarily crash at the moment the heap is corrupted, but sometime later. Not easy to debug, but, as Jon Watte pointed out, “some of these bugs can be found by running with malloc debugging”. Thus:
$ cd /where/my/app/lives/ $ export MALLOC_DEBUG=true $ ./MyApp
As for the buggy code itself, look for the following:
Duplicate deletions.
free()
on a bad pointer.
free()
on an object, or delete on malloc()
'd memory.
Window deletion (the system deletes them for you).
Failing investigation, try the "ain't-programming-swell?" method (channeled by Michael D. Crawford):
“Copy your application to a new folder, and then delete, comment, or #ifdef most of the code out. Then add back in a small amount of code... Start simple and get your code working correctly with malloc debugging before making the code more complicated.”
Is SWIG useful or even possible on BeOS?
-- Backgrounder Interruption
Are you SWIGnorant? Here's a blurb from the official SWIG Web site:
“SWIG [Simplified Wrapper and Interface Generator] is a program development tool designed to make it easy to build scripting language interfaces to C/C++ programs. Its primary audience is scientists, engineers, and programmers who would like to build interactive C/C++ programs, but who would rather work on more interesting problems than figuring out how to extend their favorite scripting language or using an excessively complicated programming tool.”
“SWIG turns C/C++ declarations into a working scripting language interface. C functions become commands, C variables become scripting- language variables, and so on. By using ANSI C/C++ syntax, SWIG is easy to use, requires no modification to underlying C code, and is relatively simple to apply to existing applications.”
For more info, visit http://www.cs.utah.edu/~beazley/SWIG/.
-- Back to our program
Ernest S. Tomlinson sees a fly in the SWIG ointment:
While SWIG uses off-the-shelf objects, the essential
BWindow
and BView
s are useful
only when subclassed. Could you work around this through clever
BMessageFilter
use? In other words, could you
simulate a MouseDown()
implementation by trapping
the message in a BMessageFilter
?
Jon Watte suggests that you simply put more books on the shelf:
“You can write your own subclasses of BView
and BWindow
to use from C-based programs, which
turns all the virtual methods into function hook calls. Then run
SWIG on those. That should not be very hard, although
slightly tedious.”
Chris Herborth goes further in suggesting that you can subclass from the underlying scripting language. He offered a Python example. Unfortunately, it's all spec and mirrors right now.
Them pesky splash screens that appear when apps start up—what are they good for?
Well...to display information, for one. And to reassure the user that all is going well during initialization (if necessary), for another.
Should a startup panel be system-modal like on Windows? "No," cried the millions. Quelle surprise. Everyone agrees that windows shouldn't pop to the front unbidden.
Therefore, suggests Earl Malmrose, Be should provide API that let's you
Show()
a window without bringing it to the front.