Last week, I wrote about the process we went through in redesigning the Be web site:
Business & Marketing: The New Be Website, Part 1: Version 1.0
All told, the process took us just under 8 weeks. By way of comparison, to do a really thoroughly researched, well tested, intelligent redesign of a web site the size of Be's usually takes 4-6 months, with 4 months being rushed.
Obviously, to meet our deadline we made compromises in the design and execution. Some of them, as I detailed last week, we knew about beforehand. Others we learned about as the feedback started pouring in. And pour it did!
For the most part, the reaction to the new site was very positive. Around 80-90% of the people liked the new design. Many people who wrote, whether they liked the design or not, had excellent constructive criticisms of the new design. Some people even sent us code, which helped us fix one minor graphic pre-loading bug we had in our JavaScript (I sent that guy a T-shirt :-).
We quickly developed a list of the top 5 issues people were raising, which covered about 80% of the complaints we received (roughly in order):
Where is the Developer section?
Where is BeWare?
Hate not having news and announcements on front page
Where is the Developer Newsletter?
Various graphics issues
We fixed the Developer Newsletter issue almost immediately, by adding a link to it in the World of Be section, an entry in the Site Map, and changing the Site Search to default to searching both the Main and Classic web sites (instead of just the Main site). The complaints on that quickly went away.
The other issues were not so easy to address. For one thing, our two person team was down to 1 for a large chunk of time; Mike Popovic took a week of vacation right before PCExpo, and I was out of the office for over a month (PCExpo, wedding, honeymoon). When I returned to work, Be had gone public, and our #1 priority was getting our Investor Relations section online. All told, we didn't begin working on a strategy to address the harder parts of people's feedback until the beginning of August.
Taking the feedback and our own impressions on the site, we developed a two-stage plan to make the web site better. The *second* step is to go back, and do what we wanted to do originally, which is take the 4-6 months to research our customers', developers', partners', and employees' needs, develop a list of services on our web site that will meet those needs, and implement a new web site that offers those services. We're calling that Version 2.0 of the web site.
To address more pressing needs, the glaring flaws in the new site, we developed a detailed list of things we could do quickly. These high "bang for the buck" changes became known as Version 1.1.
Unlike many software products, we're rolling out components of v1.1 as we develop them. By now we've made a number of changes, primarily to the front page. Things you're likely to have noticed (not including additions we've made in content):
Developers button on the front page
A new Headline News section on the front page with catchy graphic
An accompanying Headline News page with more detail
The "Buy Now!" button on the front page
Major editing of the copy on the front page (shorter, more helpful)
A new Software for BeOS section, for BeWare and other software links
We have a lot more in our queue, too. I'll share a few selected bits here. We're not publishing a specific timeline, but you should see elements from the following list appearing over the next 2 months.
A Software for BeOS button in the blue side navigation bar (replacing BeOS in Action, which will move to the Products section)
Improved section sub-navigation graphics (not grayed out)
Title attributes for all navigational elements (some browsers display this as a ToolTip when you hover over that element)
Improved SmartTemplates, with better JavaScript and better graphics management (will improve browser cache hits)
Better search results page
Additional front page design improvements
Improved graphics format / compression
Improved, more consistent terminology used throughout the Be site
New sections and additional content
Database-driven listings of various resources
Many improvements in our back-end technologies, to make publishing new information easier
The place to keep your eyes for improvements and additions to the web site is the What's New page (which is sort of hidden in the Website section, another thing we'd like to fix):
<http://www.be.com/website/whatsnew.html>
You might notice something absent from the above lists, namely, converting more of the "classic" Be web site to the new design. That *is* on our list, but it's proceeding slowly. The reason is, we're evaluating every section, every piece of content, and we're not moving things that have not been thoroughly revised and updated. With the Web Team's limited resources, and a higher priority given to new content, upgrading classic content will probably still be happening in the year 2000.
Some sections, notably BeWare and the Developers section, *are* selected for major overhauls, and will be converted to the main site at that time. Other parts of Be will be participating in these overhauls, and we expect to deliver greatly improved information and services in these areas.
Ultimately, though, how good the Be web site is will depend on continuing to get good feedback from our site visitors, and on how well we listen to it. I welcome your comments and suggestions, at <alderete@be.com> (but if you've already sent it to <webmaster@be.com>, I have it, no need to send again :-).
We're also actively researching the needs of our various groups of visitors, an activity we hope to increase considerably as a part of the Version 2.0 design process. Don't be surprised if we ask some of you to participate in an online survey, or a focus group, some time in the next 6 months!
I'm Allan, the build guy at Be. That means that this article isn't going to be about the kernel. I build the CDs that go directly to you folks and to OEMs. I also make the update packages, using the venerable PackageBuilder. This is the sort of job that takes an eye for detail and meticulous attention to the state of your product—especially with a project the size of the BeOS. The pieces of any project can get mixed up, however...leaving you wondering just what build your beta testers have, and where you put that old build. Wait, did version 0.8a12 have the same scrumpy twidlator as 0.8b5?
Being organized is one solution. Being Lazy (in the Larry Wall sense) is another.
The tools I use to try to keep this beast in line are many. One of the
most useful is 'setversion'. Use it. Put a line in your makefile so that
every new build gets stamped with your current revision. Tack an internal
version onto your app with a build counter. Or just stamp it with the
date until you're ready to release it. If you use the BeIDE, make a
resource, or put it in a .r file that gets dynamically updated, or put a
shell script in your project. Set a variable called APP_VERSION
or
something to use the abstruse syntax required—something like:
APP_VERSION== -app 1 2 4 f 0 -short 1.2.4 -long "1.2.4 "'echo -n -e '\302\251''" 1999 The Free Software Foundation".
This tells us that gzip (in this case) has an application version number of 1.2.4, and is a final release. (The syntax for version numbering is major middle minor variety internal, where variety is alpha, beta, final, etc.). The escaped characters in the echo are for the copyright (left?) symbol, natch. Now, if I do a Get Info from Tracker, there it is all beautiful and official looking. That's great if you want to quickly distinguish between builds. But what about some more automation, such as during installation? Say your app ships with a library. To make sure that your package doesn't clobber a newer version, keep your version numbers up to date and choose the "Replace if newer version" option in PackageBuilder.
Curious how to get at the details of the version info? It would be much more handy to have the numbers than "blah blah copyright blah blah" if you want to check for a greater revision. Well, it's pretty simple:
version_infoinfo
; version_kindkind
=B_APP_VERSION_KIND
;BFile
file
("versioned app",B_READ_ONLY
);BAppFileInfo
appFileInfo
(&file
);err
=appFileInfo
.GetVersionInfo
(&info
,kind
);
You can find a slightly more fleshed out example in this program (suitable for shell scripts):
<ftp://ftp.be.com/pub/samples/storage_kit/Version.zip>
With this tool, you can extract detailed information about a system's state (version /system/Tracker) and make intelligent choices about your installation. Knowing that a settings file has changed places or formats from 1.x to 2.0 means that you can massage things just right. It can be good to have fine detail of control, and having your stuff well and automatically versioned is one way of doing that.
Surely by now your project has demanded the use of some form of source control? There are two popular methods of source control available for the BeOS: CVS, made increasingly popular by the many open-source repositories that have cropped up as a way to keep the latest source easily accessible; and Perforce, which is an attempt at doing source control "right"—faster, with different features. CVS is great. You'll need it to get the freshest source to Bezilla. Perforce is great, too. We use it. So do the Perl people, I'm told. Perforce costs money, if you aren't working on an open-source project. Anyhow, set up a simple source repository. You can make it local to your machine if you like. Then, not only can you easily roll back the stupid changes that you made late last night, but you can use labels (or tags) to keep track of what consists of a version. That way, you can always go back to a previous release, even if your coding partner went crazy and rewrote the world.
Is this really lazier than keeping all your code on the desktop and zipping it all up when you want to unlease it? Yes. It'll make life easier a version or two down the road, and you can saunter from project to project.
Get CVS from <http://www.ninemoons.com/GG/>
Get Perforce from <http://www.perforce.com/>
There are many other tools that make your software projects easier to manage. Building the infrastructure can really help out as programs and teams get larger.
One question we field regularly in DTS is how to detect and handle
multiple mouse clicks. On the face of it, it's not entirely clear what a
poor application is supposed to do; after all, the BView
class's
MouseDown()
method doesn't supply any information other than the location
of the click. That alone isn't enough to distinguish two single-clicks
from a canonical "double-click"; one needs the timing information as
well. Or does one?
The Interface Kit is, indeed, keeping an eye on its internal stopwatch
when it reports mouse events to your application. The user-configurable
timing threshold is compared against the time between mouse-down events;
as the user accumulates multiple clicks within that threshold, a click
count is incremented. That count is passed to your application along with
each mouse-down BMessage
, in a field called
(naturally enough) "clicks."
All this begs the question of how to access the information. Mouse-down
events are handled behind the application's back, so to speak; the
MouseDown()
method is called invisibly from within the BWindow class's
message dispatching logic. But there is a solution! BWindow
s are, like
all BeOS message recipients, descendants of the BLooper
class, and that
class provides a way for its methods to inspect the BMessage
currently
being dispatched. The way a BView
can determine the cumulative click
count associated with a MouseDown()
call is to look at the current
message being handled by the view's looper—i.e., its window.
You can see an example of this in today's sample application, called DoubleClick, available at this URL:
<ftp://ftp.be.com/pub/samples/intro/DoubleClick.zip>
The DoubleClick application displays a window that indicates how many
consecutive clicks it receives. That determination is easy under the
BeOS, and doesn't require any calculations or state maintenance within
the application's code. In the DoubleClickView
's
MouseDown()
method, the
following two statements occur:
BMessage
*msg
=Window
()->CurrentMessage
(); int32clicks
=msg
->FindInt32
("clicks");
That's all! The application can now choose an action to take based on the
click count. This implies that a multiple click consists of several
distinct MouseDown()
events, the first of which is indistinguishable from
an ordinary single- click action. This is deliberate: multiple-click
actions must always build on their fewer-click predecessors, not replace
them. A good example is the traditional word processor click sequence.
The first click positions the insertion point, the second highlights the
word around that point, the third highlights the entire line, and the
fourth selects the entire paragraph. Each stage is an extension of the
previous one; this is good design.
There is other information in the mouse-down BMessage
, such as the
identity of the button that was pressed and the click location in the
view's and the main screen's coordinate systems. This raises issues that
complicate the subject of multiple-click detection. First, does it make
sense to consider two clicks that are "far apart" on the screen to
constitute a "double-click," presumably with modified behavior? Second,
does it make sense to treat a rapid sequence of clicks by different mouse
buttons as a "multiple click?"
DoubleClick doesn't address the first issue, but it does handle the second correctly. Briefly, the Official BeOS User Interface Decree is that a "double-click" means two clicks of the same mouse button, uninterrupted by other mouse buttons. It turns out that supporting this definition involves some extra bookkeeping on the application's part, because the mouse-down messages' "clicks" fields continue to increment even when the user presses several different buttons in quick succession. A fast primary-secondary-primary mouse button sequence winds up with the third mouse-down message indicating a "clicks" value of 3. This is patently wrong; each of these clicks should be treated as a distinct single click. A tangled situation, no?
The way DoubleClick unravels this snarl is to maintain two extra pieces of information that describe the ongoing mouse- click sequence: the identity of the last button pressed and its own click count specifically for that button. Whenever a new button is pressed the ongoing count is reset to one. Similarly, if the "clicks" field in the mouse-down BMessage is ever equal to 1, that means the multiple-click timeout has expired and click counting should be reset. The code that implements this logic looks like this:
BMessage
*msg
=Window
()->CurrentMessage
(); int32clicks
=msg
->FindInt32
("clicks"); int32button
=msg
->FindInt32
("buttons"); // is this a continuing click sequence? if ((button
==mLastButton
) && (clicks
> 1)) {mClickCount
++; } elsemClickCount
= 1;mLastButton
=button
;
At this point, mClickCount
is the correct number of clicks of the
relevant mouse button. The application should use that value in deciding
the appropriate action.
This week's installment of Rephrase 0.1d4 reveals how Rephrase knows when to quit.
The code for this article is the same as last week's:
<ftp://ftp.be.com/pub/samples/tutorials/rephrase/rephrase0.1d4.zip>
Quitting an application seems simple enough. When you send the
BApplication
a B_QUIT_REQUESTED
message, it walks through all of its
windows and calls their QuitRequested()
function. If
BWindow::QuitRequested()
returns true
,
BWindow::Quit()
is called and the
app moves on to the next window. If QuitRequested()
returns false
, the
process stops there.
The real question is how to determine if it's time to quit. The answer is straightforward in one instance: if a Quit menu item is selected, it's sent to the app, which starts to tear everything down.
In general, BeOS applications should also quit whenever their last window is closed. More specifically, they should quit when their last important window is closed, but not all windows are important in determining whether an app should quit.
About boxes and document information windows are usually considered supporting windows. An about box is purely informational and a document information window is less useful without the actual document window. Document windows and some file panels, on the other hand, are very important. When the last important window closes, the app should be told to quit, taking any supporting windows that are still visible with it.
Keeping track of important windows requires extra work.
BApplication::CountWindows()
returns a count of all windows associated
with the app. This would include supporting windows and hidden file
panels, neither of which should prevent the app from quitting. So an
application needs another count to track important windows.
As important as the count itself is the methodology for incrementing and decrementing the count. After much discussion, we've come up with these guidelines:
The application object holds a count of important windows.
The count is incremented directly from the application when it creates a window.
The count is decremented when a message is received that an important window is closed.
Open panels need to count as important windows when they are shown.
The purpose of these guidelines is to eliminate potential race
conditions. For example, if the important window count is incremented
through messaging, a race can occur if the last document window was
closed immediately after selecting a list of files from the open panel.
The new windows created in response to the open request could enqueue
their notifications after the WINDOW_CLOSED
message for the last visible
window. This in turn would cause the app to quit almost immediately after
creating the newly opened windows.
An almost identical problem can occur if the open panel is not considered an important window. In this case, the application would quit when the last document window closes, even if the open panel was showing or had just been used to choose additional files to open.
One implication of this system is that the creation of new important windows needs to be serialized through the application object. Supporting windows can still be created without the app's help, but to keep the count in sync, the app needs to create all important windows.
pEdit
is a new subclass of BTextView
.
Its main job is to keep track
of whether a text view has changed. It does this by marking a "dirty"
flag whenever text is inserted into or deleted from the view.
[pEdit.cpp
:52-64]
Closing a phrase display window should save its content if it is in
a dirty state. A pDisplay
checks the state of the text view and if
it's dirty, creates an alert asking the user if it wants to save.
If a pDisplay
needs to save when asked to close, it sets a quitting
flag and returns the results of the Save()
function. Save()
returns a
boolean value denoting whether the save has been completed by the time
it returns. If a save file panel needs to be shown, it returns false
,
as the window needs to wait asynchronously for the result.
[pDisplay.cpp
:129-162 & pDisplay.cpp
:334-350]
In MessageReceived()
the phrase display window checks for the
quitting flag when handling B_CANCEL
messages from the save panel. If
the quitting flag is set, it posts a B_QUIT_REQUESTED
message to
complete the closing of the window.
[pDisplay.cpp
:190-197]
Rephrase increments the phrase count whenever a new phrase display
window is created. The phrase count is also incremented when the open
panel is shown, to make sure the application doesn't quit until a
response from the file panel is received.
[Rephrase.cpp
:130-132]
Rephrase decrements the phrase count whenever a phrase display
window or the open panel is closed. The open panel sends a B_CANCEL
message when it closes. The phrase display windows send a
WINDOW_CLOSED
message to the app from
the pDisplay
destructor.
[Rephrase.cpp
:137-146 & pDisplay.cpp
:126]
Save panels don't count as important windows, as they are
associated with a phrase window (which does count). Instead, the
phrase window is responsible for dealing with the save panel, and
doesn't send the WINDOW_CLOSED
message until after it has the info it
needs from the panel.
Use the phrase count in ReadyToRun()
to determine if an initial
phrase display window needs to be created.
[Rephrase.cpp
:29-38]
All the current sizing and resizing functionality in Rephrase is
related to the text rect of the text view. We have moved this
functionality into the new pEdit
class.
[pEdit.cpp
:15-38]
pDisplay
keeps track of a static variable for naming empty
documents. All windows are created without a title, but the title is
set when a phrase is loaded. If the title is still empty afterwards,
it's set to "Untitled X" where X is the current untitled count.
[pDisplay.cpp
:104-112]
The Quit menu item should correctly target the app. In previous
versions this item targeted the window, causing different behavior
when using the keyboard shortcut (the app quits) and selecting the
menu item (only the window closes).
[pDisplay.cpp
:295-297]
Two weeks ago, I discussed some imprudent statements I'd made—and immediately made another one: The second part of our IPO story will appear next week. In keeping with one of our industry's less endearing but nonetheless enduring traditions, I slipped the schedule. My excuse was that I wanted to attend to the PowerPC issue. Following an e-mail barrage, I attempted in last week's newsletter to explain our position vis-à-vis IBM's announced PPC hardware and Apple's G3 and G4 machines. For a recap see "Chips Questions"
Now, back to our IPO story. Wes Saia, our CFO, was looking at the weather charts. This would be his third IPO and, in his experience, summer months, August in particular, weren't good. Events have proved him to be almost too right. In any event, Wes put us on a forced march to draft and file our S-1 in 21 days, close to a record for this complex process.
The S-1 stands for "Form S-1 Registration Statement Under the Securities Act of 1933," a document filed with the Securities and Exchange Commission. To mix formal and informal language, the S-1 describes the "deal" for the investors, the pros and cons of buying the proposed securities—a description made in a highly regulated, some would say ritualized, fashion.
The most intense part of S-1 preparation occurs in marathon sessions at "the printers." In this case, we mean financial printers, companies such as R.R. Donnelly, who help with documents involved in various financial transactions, mergers, acquisitions, IPOs, debt offerings, and the like. When type was set by hand or with Linotype machines, one sat in the printer's office and edited proofs. Printers made room in their offices for groups of people arguing over the documents being composed. When typesetting and printing became DTP, meeting rooms became the center of the business.
The Palo Alto offices of R.R. Donnelly make that point rather eloquently. In order to facilitate the drafting and filing process, they're open 24 hours a day, with staff and facilities the best hotels might envy. There's valet parking, a very good cafeteria, and shaving cream, lotion, and eye drops in the men's room. Meeting rooms have network connections, conference telephones that work, and a staff with a pleasant can-do attitude. Not to mention champagne and cigars (in politically correct Palo Alto!!) for celebrations.
There are two ways to look at this. Either it's another effete display of opulence or it's an extremely effective life-support system for this part of the IPO process. As far as I'm concerned, I saw red eyes and pale faces—and gratitude for the tools of the trade and the creature comforts.
In this context, the drafting process is a strange mixture of a collaborative and an adversarial process, involving company executives, public accountants, investment bankers and counsel for both the company and the bankers. Arguments range from esoteric accounting issues to something that makes marketing people very sad: removing adjectives. We also had to deal with what is known as Plain English, a relatively new SEC rule requiring filings to be readable by normal humans, as opposed to lawyers and bankers. Unsurprisingly, industry insiders aren't crazy about this restriction. We have our way of writing these statements, and it works—but it's also important for individual investors not to feel shut out by language. I was impressed by the "Plain English" comments we got back from our examiner at the SEC—good suggestions to improve readability, without nit-picking, obviously from a very literate individual. Not the stereotypical civil servant, especially considering my cultural origins.
The requirement for complete and accurate disclosure isn't limited to exquisitely painful accounting disquisitions; there's also what's known as the Risk Factors. There, the logic of the process dictates spelling out in detail everything that could go wrong. This makes for painful or comical reading. Management could be incompetent, the technology could be irrelevant, the market nonexistent, the competition muscular and ruthless, the Year 2000 could kill us, and, if none of that happens, lawyers could take us to the cleaners, to name but a few risks in the litany.
Finally, on May 6th, we filed our S-1 with the SEC, expecting comments in about 30 days. In the midst of the busiest IPO season ever, we were fortunate to get SEC comments on schedule. About half the comments were the "Plain English" ones already mentioned and, in total, the volume was less than half of what is considered typical for a filing like ours. Just as important, none of the comments raised difficult, show-stopper issues. This in itself was a compliment to Wes and the rest of the team. Speed had not harmed quality.
Now, we were faced with a decision. We could file our response and wait for the SEC's return comments, or we could move to the next step, the Road Show, and spend over $10,000 printing a version of our filing called "the reds" to distribute to the investors we were going to meet. The risk was that the SEC could raise difficult issues, material enough to force us to print a new set of reds or, worse, raise questions in the minds of investors. Encouraged by the initial response from the SEC, we decided to go ahead with to the next phase, the Road Show—72 presentations to investors in Europe and the US.
I had heard many Road Show horror stories. Fortunately, most proved untrue, as I'll discuss next week in Going Public: Part III—unless e-mail traffic decides otherwise.