The new Expander application in PR2 started out as a simple idea, at least in my mind; maybe that says something about how I think, but we won't go into that here. As the project progressed I found I eventually had to deal with issues like providing additional features that people requested, and an interface that people could use.
Perhaps inevitably, it was another Be engineer who submitted this bug report:
"When a app needs to be expanded I am not allowed to expand into a selected folder."
Robert says that if you go up one level higher you then get to choose the folder.
This is dumb."
What the bug report was referring to was the process of selecting a
destination folder via the default BFilePanel
I was using in the
application. If you were "in" the folder you wanted to select, you had to
go "up" one level to select it, rather than being able to select it
directly
Okay, so maybe the interface was a little confusing. And, well, maybe I was just being lazy and did not really want to add another feature to this supposedly simple application. But with a bug report like that (and from Melissa, no less), I was just going to have to do the extra work.
A BFilePanel
can be constructed so that all displays are directories, as
well as allowing the user to select a directory instead of a file. The
goal for this article is to create a custom FilePanel, using the
BFilePanel
class, which allows for the selection of any directory, while
you are in it or while you are browsing its parent.
In browsing the header file, FilePanel.h
, you will find a nice, clean,
self-contained class. Unfortunately, there does not seem to be a simple
way of just adding a view, a button in our case, to the window.
Fortunately, there is one method provided, Window()
, that returns the
FilePanel's window. If you can get the window, you can get its views, and
if you can get the views then you can add to them.
To accomplish this task a new class must be derived from BFilePanel
. The
constructor has been simplified for clarity, the SelectionChanged()
method has been included, and a BButton
has been added:
classTDirFilePanel
: publicBFilePanel
{ public:TDirFilePanel
(BMessenger
*target
,BRefFilter
*filter
=NULL
); voidSelectionChanged
(); private:BButton
*fCurrentDirBtn
; };
To set up this custom FilePanel and provide the basic functionality requires six steps in your code:
Retrieve the window from the FilePanel object.
Find the first child of the window and then find the view named "cancel button".
Create our new BButton
, size it appropriately for its title, and
place it relative to the button.
Set the target for the new button.
Set the message for the new button; part of the message will be the
entry_ref of the current directory. The current directory is retrieved
via the GetPanelDirectory
method and
the resulting entry_ref is added
to the message. When the user clicks this button it will send this
message to the target (BMessenger
) signifying a new directory
selection.
In the target's MessageReceived
method, where we catch the message
sent from our FilePanel, we need to do one more thing, send the
FilePanel a Hide()
message.
By setting the node_flavor parameter to B_DIRECTORY_NODE
, the FilePanel
allows the selection of directories instead of files; take a peek at
SupportDefs.h
to see the rest of the node flavors.
TDirFilePanel
::TDirFilePanel
(BMessenger
*target
,BRefFilter
*filter
) :BFilePanel
(B_OPEN_PANEL
,target
,NULL
,B_DIRECTORY_NODE
,false
,NULL
,filter
) {BWindow
*w
; // 1) Get the window from the FilePanel // so we can find the window's childrenw
=Window
(); if (w
->Lock
()) {BRect
btnrect
; // 2) Find the first view in the window, // and from that view we find the cancel buttonBView
*v
=w
->ChildAt
(0);BView
*cancelBtn
=v
->FindView
("cancel button"); if (cancelBtn
) {BView
*parentview
; floatcharWidth
; // 3) Construct and add the new 'Select Current // Directory' buttoncharWidth
=cancelBtn
->StringWidth
("Select Current Directory");btnrect
=cancelBtn
->Frame
();btnrect
.right
=btnrect
.left
- 10;btnrect
.left
=btnrect
.right
-charWidth
- 40;fCurrentDirBtn
= newBButton
(btnrect
, "current dir button", "Select Current Directory",NULL
,B_FOLLOW_RIGHT
+B_FOLLOW_BOTTOM
); // 4) Set the target for the new buttonfCurrentDirBtn
->SetTarget
(*target
);parentview
=cancelBtn
->Parent
();parentview
->AddChild
(fCurrentDirBtn
); // 5) Set the message for the new button entry_refref
;BMessage
*msg
= newBMessage
('slct');GetPanelDirectory
(&ref
);msg
->AddRef
("refs",&ref
);fCurrentDirBtn
->SetMessage
(msg
); }w
->Unlock
(); } }
Now we come upon another minor problem. When a user begins traversing
directories, the entry_ref in our new button also needs to change.
Luckily in BFilePanel
there is a method,
SelectionChanged()
, that does
just the trick. As the user clicks on a file or moves up or down in the
directory structure, this method will get called. By calling
GetPanelDirectory
we can then retrieve the current directory and update
the button's message.
voidTDirFilePanel
::SelectionChanged
() {BWindow
*wind
;wind
=Window
(); if (wind
->Lock
()) { entry_refcurrRef
;GetPanelDirectory
(&currRef
); // modify the btn's msgBMessage
*msg
= newBMessage
('slct');msg
->AddRef
("refs",&currRef
);fCurrentDirBtn
->SetMessage
(msg
);wind
->Unlock
(); } }
One final bit to help simplify our scenario. As we are traversing directories looking for a folder for our destination, we notice that every file is still visible through the FilePanel, even though we only want to select directories. The trick is to add a filter to the FilePanel.
Using a BRefFilter
to do this is quite simple and only requires deriving
a new class from BRefFilter
and adding to its Filter()
method.
classTDirFilter
: publicBRefFilter
{ public: boolFilter
(constentry_ref*e
,BNode
*n
, struct stat*s
, const char*mimetype
); };
The Filter
method receives all the information you will need to discern
whether you want the file or directory to show. In our case we are just
looking for directories and volumes and only need to look at the mimetype
to do so.
Note that if the item is a symlink to a directory or a volume, this filter will not see it. I will leave that as a mini exercise for the reader.
boolTDirFilter
::Filter
(constentry_ref*e
,BNode
*n
, struct stat*s
, const char*mimetype
) { if (strcmp
("application/x-vnd.Be-directory",mimetype
) == 0) returntrue
; else if (strcmp
("application/x-vnd.Be-volume",mimetype
) == 0) returntrue
; else returnfalse
; }
To use this RefFilter in the directory selection FilePanel above, you simply construct the custom FilePanel with the Filter object as its parameter:
TDirFilter
filter
;TDirFilePanel
*dirSelect
= newTDirFilePanel
(newBMessenger
(NULL
,this
),filter
);
So there we have it. A cleaner way to select directories, an examination
of a simple class, BFilePanel
, which we have all probably used, and a
peek at using a BRefFilter
to simplify the view that we present to our
users. The addition of simple features such as these add a polished look
to an application that a user definitely notices and appreciates.
Back in Newsletter 2-#32, Robert Polic wrote an article about spamming using the mail and query features built into the BeOS. The article provided an excellent illustration of several concepts in BeOS programming, including the basics of writing a command line application, using BeOS queries, and sending e-mail programmatically.
It was written with tongue firmly in cheek, but actually generated some fairly heated responses from people who apparently had received more spam than they'd like, and didn't want to see any more.
The thing is, writing a spamming tool is pretty trivial. Robert did his in just over a hundred lines of code, and I've even written one myself -- and I'm *not* a software engineer.
The hard part of being a spammer is getting all the e-mail addresses to which you plan to send your junk mail. Collecting and managing this database of addresses and, presumably, demographic information is definitely where the "value" is in the life of a spammer.
Robert's spamming tool simply assumed the presence of a collection of BeOS Person records, files which are specially marked and which have extra, people-related attributes like "Name", "Phone Number", and "E-mail" attached to them.
Person files are really cool. You can drag them onto BeMail to create a new message to those Persons, or drag them into an existing message to add them to the recipient list. There's a pop-up menu for the To:, CC:, and Bcc: fields in BeMail, which lets you choose Persons you've created, and AdamMail (and other applications, too, I'm sure) provides quite nifty expansion of e-mail addresses, also based on the Person files on your system.
Unfortunately, creating such Person files was, until recently, not terribly easy. With the Preview Release 2 (PR2), we've added a new application called People. It's my second favorite thing that's new in PR2. While it's not an industrial strength contact database (and therefore of little use to spammers), it does provide an easy way to create and update Person files. I think you'll find it quite handy!
My favorite new thing in PR2 has to be the changes to the Find function. While I find the ability to search across multiple volumes (disks) or only specific volumes useful, I'm *thrilled* about being able to edit (modify) the Find criteria.
Try this (when you get your PR2 CD, of course!): Do a find for all e-mail from the domains "cyberpromo.com", "savetrees.com", or contains "free" in the subject line (hint: detailed directions for doing this can be found in a BeOS Tip of the Week: http://www.be.com/users/tips/tip7.html). You'll get a results window with a lot of spam in it. Select All (Command+A) and Move To Trash (Command+T), and 3 seconds later you're spam-free.
Of course, you missed another notorious spammer, in the domain "pleaseread.com". In PR2, it's simple to add this evildoer to your killfile. Choose "Edit Query" (Command+G) from the File menu of the results window, make your changes or additions, and click "Find" again. Cool, n'est pas?
As the keeper of the Be FAQ lists, I've written most of our instructions for dealing with various compressed or archived files (.zip, .tar, .gz, or .tgz files, mostly) people download from the Internet. The instructions were long and required the command line, which turned a lot of people off—but no more! In PR2, we have a nifty new Expander utility, which makes short work of such archives.
It's a drag-and-drop tool, but in PR2 NetPositive will automagically hand off downloaded files to Expander for automatic extraction. The coolest part, though, is that now your archived files will have nifty Be-style icons!
Speaking of NetPositive, it's seen quite a few improvements in the way it handles HTML which used to be problematic. The Webmaster and I are pretty happy about that, and so will you be when you get it.
There are, of course, other things which changed in PR2, but I'll let you read the Release Notes for details. I'll just mention two last things that I like a lot. Tracker columns are now "live" as you resize them, and the Tracker is smart about columns like Modified: If you make it narrower, the Tracker will abbreviate or change the format in which it displays the information, going from "Friday, October 10 1997, 05:54:12 PM" to "Oct 10 1997, 05:54 PM" to "10/10/97", as necessary. Try it, and other columns, and see!
Last there's the obligatory (and new) About BeOS box, which displays system hardware and software version information. It's got more than that, though you'll need to play around a bit to see it (but did you know that Hiroshi is his *middle* name?).
That's all for this article, but be sure to read the Release Notes on the PR2 CD for more (and there's a fair amount more). Then you too can do your own Julie Andrews imitation.
Can you believe it? This is my one year anniversary of working at Be, Inc. I first joined Be from my cozy self- started company of 12 years because I figured I liked programming the BeOS so much, I might as well participate directly. As I told Jean Louis when I was interviewing:
I've pushed the horse, when programming with NeXTSTEP. I've pulled the horse, when programming with Taligent. Now I want to ride the horse.
In the past year, my daughter has grown, I've corresponded with developers around the world, and we've put out a few releases of the BeOS. Quite a lot of activity for a small company. When I joined, I invented a new title for myself: "Technical Evangelist." An evangelist who knows how to program.
In a way, all engineers are technical Evangelists. If you've been programming the BeOS for any amount of time, you have probably seen the names of many Be engineers in newsgroups, on mailing lists, and here in this newsletter. This might lead you to believe that all Be engineers are part of the marketing organization.
Almost as soon as I joined Be, I was asked if I would fly to Washington in a couple of weeks to give demos, write a magazine article for MacTech magazine, pronto, and write a newsletter article. Well, I did it all, and the last task I've kept on doing ever since. Then I was asked to help hire a boat load of people, and get our DTS department started. I did that with varying degrees of success, which you can judge for yourself.
One aspect of this job that strikes people on the outside looking in, is how can anyone write every week about this stuff? Well, it's easy. If you examine your own programming, you'll find that there is a lot of excitement that occurs on an everyday basis. You're a real programmer's programmer aren't you? Yasmin ran a temperature... that reminds me, the BeOS is hot! Or as one of our own engineers put it: "The BeOS is the best operating system! I really like programming it."
What is it that drives people to make such comments about the BeOS? Is it the multithreaded programming model? Probably this and many other minute details that are continually exposed on a daily basis. Multithreaded programming can be a tremendous benefit to many programs, but not all OS implementations are alike. And even if they were, multithreaded programming demands a certain regimen or it can lead to little or no improvement in performance. In the past two articles I went over spinlocks wrong and right, but what good are any locks?
Let's take a simple task. You want to render a scene. You want to use multiple threads to do it. We can break this task up into multiple phases. Let's assume that the scene can be rendered by taking the individual objects in the scene and rendering them. If you had a thread per object, you would gain great parallelism. Well, we have many thousands of objects in the scene, so we don't necessarily want to do that, but we do want to use a pool of say 25 threads. The scene is described in some file format. Our task is to parse the file into objects, and place these objects onto an assembly line for the object renderers to take off.
Thread 1—Parse the scene, place objects onto the rendering line.
The rendering assembly line is represented by a FIFO data structure. This is little more than a list with some ordering semantics.
fifo
::push
(aObject
)fifo
::pull
(aObject
)
All you have to do in the parsing thread is push objects into the line as you parse the file.
ALockClass
gLock
;AFIFOClass
afifo
;parser_loop
() { while(get_next_object
(aObject
,file
)) {gLock
.Lock
();afifo
.push
(aObject
);gLock
.Unlock
(); } }
Threads 2-26—Pull an object off the line and render it.
In a loop, each rendering thread does this:
loop() { while (afifo
.pull
(aObject
)) {gLock
.Lock
();afifo
.pull
(aObject
);gLock
.Unlock
();render
(aObject
); } }
Notice that any time you want to access the fifo object, you have to explicitly lock it. That is to guarantee that it remains in a consistent state while you are adding and removing objects. It would be bad to add objects and possibly change some internal value while you are deleting and causing a counter change in that same object, and thus you need the locks. They guarantee an atomic action is occurring.
But this form of usage can become cumbersome at times. You have to remember when to lock what because there is nothing preventing you from just accessing the fifo object directly without locking it.
One of the things you can do is to move the locking inside the object that is being locked. This vastly simplifies the code and helps alleviate many problems that are typically very hard to detect and fix. So instead of relying on the programmer to lock at the right times, do it for them.
afifo
::push
(aObject
) {fLock
.Lock
() // do the push thingfLock
.Unlock
() }afifo
::pull
(aObject
) {fLock
.Lock
() // do the pull thingfLock
.Unlock
() }
That way, the locks in the rendering and parsing threads can go away, and
your day is much lighter. So why doesn't BList
implement things this way?
Because it is a very low level primitive that is meant to be as light
weight as possible. It can be utilized in other data structures like this
fifo, which provide the appropriate locking semantics. "Don't use locks
until you have to" is a basic programming idiom in the BeOS. And its
companion, "Use locks where necessary."
Like I said, anyone can probably look at the 200 lines of code they write a day and turn it into a newsletter article. Life as a programmer can be fun and exciting... If you can put the right spin on it.
So, it's my one year anniversary. I'm still as excited about Be as I was before I joined. Even more so today as we move into greater markets like the Intel space, and the OS itself becomes more and more substantial. There are a lot of fantastic engineers here, and our marketing over the next year should make the "Best OS ever" statement more apparent to more programmers, which will produce more apps, which are just the best things since sliced code.
Have fun at the front.
Once again, e-mail traffic suggested a topic for this week's column: Several honorable correspondents wanted to know our position regarding the Java war. Yes, we have one: We are unequivocally ambivalent.
Let's start by stating I'm not addressing the merits of the language, they are many, they are not infinite and Java could be the Basic of the Web, or much more. I just wish things would settle a bit more and allow us to better understand what Java's best and worst uses are; despite Rear Admiral Grace Hopper's contention, no one really wrote an operating system in Cobol. In any case, Java has achieved currency and that is that.
What is not settled, it appears, is how much Sun controls Java. Sun says they decide what Java is and its licensees are bound by the definition. Here, bound means if you ship a Java-based product, it must pass a series of tests demonstrating it complies with Sun's definition of Java.
Two events appear to have triggered Sun's breach of contract lawsuit against Microsoft. The first is less of an event than it is a habit: Microsoft "embraced and extended" Java, adding features to their version of the language. These features were designed to "connect" Java programs more closely to Windows.
This was explained as being dictated by performance or expressive reasons: Microsoft Java programs would run faster that way or they would access Windows resources more easily, or their look would be more consistent with other Windows programs.
This, of course, violated the "spirit" of Java: Write once, run everywhere. In that sense, these programs were no longer Java programs, they were Windows programs written in a language called Java, just as other Windows programs are written in C or C++.
One more thing before we move to Explorer 4.0: Microsoft managed to ship good Java programming tools and the fastest Java Virtual Machine. Who could say their embrace of Java was half-hearted?
As to Explorer 4.0, the second "event," it appears its Java components miss two methods, the Java Native Interface and the Remote Method Invocation. It seems the omissions were neither random nor capricious: I'm told by higher technical authorities a "full" implementation of Java would facilitate the remote execution of Windows programs. In other words, from an Excel-less computer, you'd be able to run a copy of Excel residing on another computer on the network.
More specifically, Network Computers, NCs, are often derided as standing for Not Compatible by their Microsoft opponents. If the "remote Excel" theory isn't too far from technical truth, suddenly, NCs would be able to run legacy Windows programs, albeit remotely. Citrix got into a dispute (now resolved) with Microsoft over running Excel or Word from "dumb" terminals connected to a modified Windows NT server, and Microsoft itself published a Hydra white paper describing their plans for a similar product offering.
With this in mind, we can turn to what the "Java war" is really about. It is not about the merits of Java as a programming language, it is about finding a way to topple Microsoft from its dominant position on the desktop. Create a modern programming language, bundle it with a run-time environment that isolates the programs from the hardware and the underlying OS, use the Intranet or the Internet to run legacy applications and, presto, you have the best of the old world and the freedom from Intel and Microsoft. You can sell your hardware and your software, instead of "theirs," without their dominant position preventing you from entering the market with better, faster, cheaper hardware and software.
Would the world be a better place if we had more real alternatives? The question, as posed, is poorly framed, it doesn't leave the opportunity to disagree, it fails the "no con" test.
For the time being, the more interesting questions are what does the contract really say, and what does the law permit. On the latter, my position is the law does not currently sort the applications from the OS. As a result, Microsoft and others can extend their reach in ways that might be unpleasant for the competition, but lawful nonetheless.
As for the contract, I can't wait to see it. Microsoft has agreed to make it public and Sun seems reluctant to do so. There might be good business reasons for confidentiality, but I'd feel better if we could rely on information instead of gossip.
Microsoft has one option, pick up its Java marbles and go home. No one sues anyone over versions of C++, they could implement most of Java without calling Java, or stop using Java altogether. Windows programmers would be left with their customary 90 or 95% of the desktop market, and pure Java programmers with the rest. Is this more choice?