haiku-website/static/legacy-docs/benewsletter/Issue4-35.html

823 lines
56 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Be Newsletters - Volume 4: 1999</title><link rel="stylesheet" href="be_newsletter.css" type="text/css" media="all" /><link rel="shortcut icon" type="image/vnd.microsoft.icon" href="./images/favicon.ico" /><!--[if IE]>
<link rel="stylesheet" type="text/css" href="be_newsletter_ie.css" />
<![endif]--><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Be Newsletters" /><link rel="up" href="volume4.html" title="Volume 4: 1999" /><link rel="prev" href="Issue4-34.html" title="Issue 4-34, August 25, 1999" /><link rel="next" href="Issue4-36.html" title="Issue 4-36, September 8, 1999" /></head><body><div id="header"><div id="headerT"><div id="headerTL"><a accesskey="p" href="Issue4-34.html" title="Issue 4-34, August 25, 1999"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a accesskey="u" href="volume4.html" title="Volume 4: 1999"><img src="./images/navigation/up.png" alt="Up" /></a> <a accesskey="n" href="Issue4-36.html" title="Issue 4-36, September 8, 1999"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="headerTR"><div id="navigpeople"><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div><div class="navigboxed" id="naviglang" title="English">en</div></div><div id="headerTC">Be Newsletters - Volume 4: 1999</div></div><div id="headerB">Prev: <a href="Issue4-34.html">Issue 4-34, August 25, 1999</a>  Up: <a href="volume4.html">Volume 4: 1999</a>  Next: <a href="Issue4-36.html">Issue 4-36, September 8, 1999</a></div><hr /></div><div class="article"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Issue4-35"></a>Issue 4-35, September 1, 1999</h2></div></div></div><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Marketing4-35"></a>Business &amp; Marketing: The New Be Website, Part 1: Version 1.0</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Michael</span> <span class="surname">Alderete</span></span></div></div></div><p>
If you've been a member of the Be community for long, you know that we
recently redesigned the Be corporate web site. Many people, especially
our developers, gave us feedback on what they liked and disliked, and
lots of suggestions. To give you some insights into what we did and why,
and what we'll do in the future, I thought I'd write about the process we
went through, and what happened afterwards.
</p><p>
I took the job of Webmaster this year at the end of March. My second day
on the job I was told "We need a new web site. You have 4 weeks." After
the paramedics restarted my heart, we got to work.
</p><p>
After some consultation with outside design firms, it became clear that 4
weeks weren't enough to do the job well and at a reasonable cost. We
extended our deadline, asked our branding and design firm to handle this
project as well, and got to the hard part of the task: creating a new
organizational scheme for the site.
</p><p>
The number one complaint about the old design was that it was too hard to
figure out what Be did. The Be web site was focused on the existing Be
community, and served it well, but tended to turn off people who were
visiting for the first time. The site didn't convert casual browsers into
customers, because they would quit browsing before they learned what our
product was!
</p><p>
Since one of the groups of people we wanted to please with the new site
was first time visitors, including potential customers and investors, we
knew we had to make significant changes. (This was one of only many
considerations we had on our list.)
</p><p>
To reorganize our site, we worked on two different approaches with our
design agency, Fitch. One, which we called "The Matrix," was very
sophisticated but was also so complicated we couldn't explain it to other
people at Be. We took the simpler of the two approaches.
</p><p>
The visual design took less time. Fitch presented us with three different
visual looks. We chose one, and then looked at multiple variations on
that theme. We picked one of those, made a couple of minor changes, and
Fitch's designers turned it over to their web production team.
</p><p>
Fitch's web production group was going to build out a set of base
templates: one skeleton page for each section, and all the associated
graphics. Then Be's web team (all two of us) would take those, and build
out the site to the 20 pages we were planning to complete for launch.
</p><p>
The first iteration of the templates we got used HTML frames for the top
navigation area. Somehow we had forgotten to say "don't use frames."
(Though we were very clear that the new design could not depend on
anything that wasn't supported by NetPositive. And it doesn't, though the
JavaScript rollovers are kinda cool if you visit using another browser.)
</p><p>
For those of you who don't already know my attitude towards HTML frames,
let me be plain: Frames are Evil. They are the work of the Prince of
Darkness. I *hate* frames. I can go into detail as to why, but I'll just
link to Jakob Nielsen's two excellent articles on the issue:
</p><ul class="itemizedlist"><li><p>
Why Frames Suck (Most of the Time)<br />
<a class="ulink" href="http://www.useit.com/alertbox/9612.html">http://www.useit.com/alertbox/9612.html</a>
</p></li><li><p>
"Top Ten Mistakes" Revisited<br />
<a class="ulink" href="http://www.useit.com/alertbox/990502.html">http://www.useit.com/alertbox/990502.html</a>
</p></li></ul><p>
So we kicked the base templates back, and asked Fitch to redo them
without frames. This put us a bit over time and over budget, but getting
rid of the frames was worth it.
</p><p>
Other flaws were not so easy to fix. At the last moment, we realized that
the new site design would unveil the new Be logo three weeks ahead of our
planned "debut" at PCExpo. So we quickly hacked out a replacement graphic
using the old logo. It looked terrible, but there was no other way to
keep the new logo secret.
</p><p>
The awful logo graphic was one of about a dozen visual flaws we could see
in the new design. Some others were that the gradient background and the
small text in the blue side navigation bar combined to make it very hard
to read; the grayed-out "sub-navigation" links looked disabled and
unavailable; JPEG artifacts in the Be logo (even the new one) were
especially visible in NetPositive; and somewhat inappropriate icons were
used in the blue side navigation bar. The Zookeeper icon for the Jobs
section was especially unfortunate; rumors that Be chains people to their
desks, or flogs them when they get behind schedule, are completely
untrue. We haven't done that in almost two years.
</p><p>
We and Fitch knew about these flaws. But with the aggressive development
schedule and fairly fixed launch date, we just didn't have the luxury of
going back to fix things. At all four stages of the project
(architecture, visual design, HTML design, production) we came to a point
where we said "it's flawed but the deadline is really important; we'll
fix it in version 1.1" (a phrase I'm sure every software developer has
heard at one time or another!). We moved on to production.
</p><p>
With the templates and graphics in hand, the Web Team got to work
building out the site. We implemented the actual site templates in a mix
of HTML and the outstanding PHP
(<a class="ulink" href="http://www.php.net/">http://www.php.net/</a>), a server-side
processing language (someone please port this to BeOS!). These templates
are "smart," in that they know what page they're displaying and adjust
elements accordingly. All the work of keeping the navigational links,
highlighted graphics, and JavaScript code correct is done by those
templates, making it *vastly* easier to add new pages to sections, etc.
</p><p>
With production basically finished (we were just waiting for a few pieces
of content to trickle in), we examined our handiwork. It wasn't perfect
by a long shot, but it didn't suck completely either. At some point, you
just have to STFP ("ship the product"—the "f" is silent). So we did.
</p><p>
Uh, well, we wanted to. But at the last moment, we realized that the new
server hardware, a much beefier—and physically larger—system than
its predecessor, would not fit in the cage at our co-location facility.
We'd have to wait three days until they could rewire a nearby rack to
receive our box. "Three days" turned out to be a week...
</p><p>
In the end we launched the new site on June 2nd, a week and a day late.
Not bad, all things considered—our schedule had not allowed us to add
"padding" for delays at all; we'd barely had time to do QA.
</p><p>
The various delays had an upside, though. They and the smart templates
allowed the Web Team to expand the number of pages on the site when it
launched. Instead of the original target of 20 pages on the new site, we
ended up with 42 (we're well over 100 now).
</p><p>
Next week, I'll return and talk about the feedback we've received,
additional issues we've uncovered—and what we're going to do about it,
very soon.
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Engineering4-35"></a>Be Engineering Insights: Programming at the Limit...</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Benoît</span> <span class="surname">Schillings</span></span></div></div></div><p>
Before I dive into the subject of this week's newsletter article, I'd
like to claim the title for working in the "hardest programming
environment for programming the BeOS framework..."
</p><p>
One of my regular programming environments is my garden. I connect my
faithful dual 300 Mhz PC running BeOS to my telescope, and under the
curious gaze of Molly or Zippy (two wonderful dogs! See
www.zippydog.com), I happily program under the stars.
</p><p>
Now this is a pleasant environment. But with the comfort of my
home/DSL/refrigerator at hand, I can't really claim it as the hardest
environment for programming BeOS. So last week I went one step farther --
I programmed the same machine (still connected to the telescope) near the
summit of White Mountain in eastern California.
</p><p>
The altitude was about 11,000 feet, the wind speed about 30 mph, and the
temperature was low enough that I had to add some rum to my Diet Coke to
keep it from freezing. A Honda generator provided the power—the
fuel-efficient BeOS burns about 0.8 gallon of supreme unleaded a day.
</p><p>
Amazingly, even under something like wilderness survival conditions,
further attenuated by my highly diminished mental abilities, the Be
programming framework is still simple enough that I could program with
ease. That's the magic of BeOS!
</p><p>
Now, lets talk about something more "down to earth"...
</p><p>
A while back I wanted to add a little animated gizmo to 3dsound --
something I thought was very simple but which turned out to be a bit more
difficult to implement than I expected. I'm talking about the little
black triangle at the top left of the content window.
</p><p>
If you click on that triangle (or hit the tab key), the panel containing
the vu-meters, etc., will show/hide. I wanted this triangle to rotate
smoothly.
</p><p>
At first, I did a trivial implementation of just drawing a triangle at
the different rotations, but I wasn't very happy with the result. The
edges of the triangle were pretty huggly (that's "ugly" to you) at most
angles!
</p><p>
The best solution would be anti-aliasing, but I wanted to try something
different: using the framework to render the triangle, then blurring the
result before blitting it to the screen.
</p><p>
In this case, blurring works like a simple low-pass filter. It removes
the offending high frequency (jaggies). In general, when you do fast
animation, adding blurring tends to make the result look much better.
</p><p>
The result looks really nice (I think it's as good as real anti-aliasing)
and can be applied to many kinds of rendering.
</p><p>
Here is the code:
</p><pre class="programlisting cpp">
<span class="comment">//------------------------------------------------------</span>
class <code class="classname">TCtrlView</code> : public <code class="classname">BView</code> {
<span class="type"><code class="classname">BBitmap</code> *</span><code class="varname">b</code>;
<span class="type"><code class="classname">BView</code> *</span><code class="varname">bv</code>;
<span class="type">float</span> <code class="varname">rot</code>;
public:
<code class="methodname">TCtrlView</code>(<code class="classname">BRect</code> <code class="parameter">frame</code>, <span class="type">char *</span><code class="parameter">name</code>);
<code class="methodname">~TCtrlView</code>();
virtual <span class="type">void</span> <code class="methodname">Draw</code>(<code class="classname">BRect</code> <code class="parameter">ur</code>);
<code class="classname">BPoint</code> <code class="methodname">transform</code>(<code class="classname">BPoint</code> <code class="parameter">in</code>);
<span class="type">void</span> <code class="methodname">SetRotation</code>(<span class="type">float</span> <code class="parameter">r</code>);
<span class="type">void</span> <code class="methodname">blur</code>();
};
<span class="comment">//------------------------------------------------------</span>
<code class="classname">TCtrlView</code>::<code class="methodname">TCtrlView</code>(<code class="classname">BRect</code> <code class="parameter">frame</code>, <span class="type">char *</span><code class="parameter">name</code>)
: <code class="classname">BView</code>(<code class="parameter">frame</code>, <code class="parameter">name</code>, <code class="constant">B_FOLLOW_NONE</code>, <code class="constant">B_WILL_DRAW</code>)
{
<code class="methodname">SetViewColor</code>(<code class="varname">backgr</code>);
<code class="varname">b</code> = new <code class="classname">BBitmap</code>(<code class="classname">BRect</code>(0,0,31,31),
<code class="constant">B_COLOR_8_BIT</code>,
<code class="constant">TRUE</code>);
<code class="varname">b</code>-&gt;<code class="methodname">AddChild</code>(<code class="varname">bv</code> = new <code class="classname">BView</code>(<code class="classname">BRect</code>(0, 0, 31,
31),"",<code class="constant">B_FOLLOW_ALL</code>,<code class="constant">B_WILL_DRAW</code>));
<code class="varname">rot</code> = 3.1415926;
<code class="methodname">SetRotation</code>(<code class="varname">rot</code>);
}
<span class="comment">//------------------------------------------------------</span>
<code class="classname">TCtrlView</code>::<code class="methodname">~TCtrlView</code>()
{
delete <code class="varname">b</code>;
}
<span class="comment">//------------------------------------------------------</span>
<span class="comment">// 2d transform routine</span>
<code class="classname">BPoint</code> <code class="classname">TCtrlView</code>::<code class="methodname">transform</code>(<code class="classname">BPoint</code> <code class="parameter">p</code>)
{
float <code class="varname">sina</code> = <code class="function">sin</code>(<code class="varname">rot</code>);
float <code class="varname">cosa</code> = <code class="function">cos</code>(<code class="varname">rot</code>);
float <code class="varname">x0</code>,<code class="varname">y0</code>;
<code class="parameter">p</code>.<code class="varname">x</code> -= 9;
<span class="comment">//offset from the rotation center</span>
<code class="parameter">p</code>.<code class="varname">y</code> -= 17;
<code class="varname">x0</code> = <code class="parameter">p</code>.<code class="varname">x</code> * <code class="varname">cosa</code> - <code class="parameter">p</code>.<code class="varname">y</code> * <code class="varname">sina</code>;
<code class="varname">y0</code> = <code class="parameter">p</code>.<code class="varname">x</code> * <code class="varname">sina</code> + <code class="parameter">p</code>.<code class="varname">y</code> * <code class="varname">cosa</code>;
<code class="varname">x0</code> += 9;
<span class="comment">//offset back</span>
<code class="varname">y0</code> += 17;
<code class="parameter">p</code>.<code class="varname">x</code> = <code class="varname">x0</code>;
<code class="parameter">p</code>.<code class="varname">y</code> = <code class="varname">y0</code>;
return <code class="parameter">p</code>;
}
<span class="comment">//------------------------------------------------------
// very simple blurring routine, also pretty fast!</span>
<span class="type">void</span> <code class="classname">TCtrlView</code>::<code class="methodname">blur</code>()
{
<span class="type">uchar *</span><code class="varname">c</code>;
<span class="type">long</span> <code class="varname">x</code>,<code class="varname">y</code>;
<span class="type">uchar</span> <code class="varname">tmp</code>[32][32];
<span class="type">long</span> <code class="varname">acc</code>;
<code class="varname">c</code> = (<span class="type">uchar *</span>)<code class="varname">b</code>-&gt;<code class="methodname">Bits</code>();
for (<code class="varname">y</code> = 1; <code class="varname">y</code> &lt; 29;<code class="varname">y</code>++)
for (<code class="varname">x</code> = 2; <code class="varname">x</code> &lt; 20;<code class="varname">x</code>++) {
<code class="varname">acc</code> = c[(x+y*32)];
<code class="varname">acc</code> = <code class="varname">acc</code> + <code class="varname">acc</code> + <code class="varname">acc</code> + <code class="varname">acc</code>;
<code class="varname">acc</code> += <code class="varname">c</code>[(<code class="varname">x</code>+<code class="varname">y</code>*32)+1];
<code class="varname">acc</code> += <code class="varname">c</code>[(<code class="varname">x</code>+<code class="varname">y</code>*32)-1];
<code class="varname">acc</code> += <code class="varname">c</code>[(<code class="varname">x</code>+<code class="varname">y</code>*32)+32];
<code class="varname">acc</code> += <code class="varname">c</code>[(<code class="varname">x</code>+<code class="varname">y</code>*32)-32];
<code class="varname">acc</code> /= 8;
<code class="varname">tmp</code>[<code class="varname">y</code>][<code class="varname">x</code>] = <code class="varname">acc</code>;
}
for (<code class="varname">y</code> = 1; <code class="varname">y</code> &lt; 29;<code class="varname">y</code>++)
for (<code class="varname">x</code> = 2; <code class="varname">x</code> &lt; 20;<code class="varname">x</code>++) {
<code class="varname">c</code>[<code class="varname">x</code>+<code class="varname">y</code>*32] = <code class="varname">tmp</code>[<code class="varname">y</code>][<code class="varname">x</code>];
}
}
<span class="comment">//------------------------------------------------------
// Set the rotation of the arrow and draw the arrow to the
// screen.</span>
<span class="type">void</span> <code class="classname">TCtrlView</code>::<code class="methodname">SetRotation</code>(<span class="type">float</span> <code class="parameter">r</code>)
{
<code class="varname">rot</code> = <code class="parameter">r</code>;
<code class="varname">b</code>-&gt;<code class="methodname">Lock</code>();
<code class="varname">bv</code>-&gt;<code class="methodname">SetHighColor</code>(<code class="varname">backgr</code>);
<code class="varname">bv</code>-&gt;<code class="methodname">FillRect</code>(<code class="classname">BRect</code>(0,0,32000,32000));
<code class="varname">bv</code>-&gt;<code class="methodname">SetHighColor</code>(32,32,32);
<code class="varname">bv</code>-&gt;<code class="methodname">FillTriangle</code>(<code class="function">transform</code>(<code class="classname">BPoint</code>(5,10)),
<code class="methodname">transform</code>(<code class="classname">BPoint</code>(5,24)),
<code class="methodname">transform</code>(<code class="classname">BPoint</code>(12,17)));
<code class="varname">bv</code>-&gt;<code class="methodname">Sync</code>();
<code class="methodname">blur</code>();
<code class="varname">b</code>-&gt;<code class="methodname">Unlock</code>();
<code class="methodname">DrawBitmap</code>(<code class="varname">b</code>, <code class="classname">BRect</code>(1, 1, 1 + 14, 1 +
30),<code class="classname">BRect</code>(0,3,14,33));
}
<span class="comment">//------------------------------------------------------</span>
<span class="type">void</span> <code class="classname">TCtrlView</code>::<code class="methodname">Draw</code>(<code class="classname">BRect</code> <code class="parameter">ur</code>)
{
<code class="classname">BRect</code> <code class="varname">r</code>;
<span class="type">long</span> <code class="varname">i</code>;
<span class="type">rgb_color</span> <code class="varname">c</code>;
<code class="methodname">DrawBitmap</code>(<code class="varname">b</code>, <code class="classname">BRect</code>(1, 1, 15, 31),<code class="classname">BRect</code>(0,3,14,33));
}
<span class="comment">//------------------------------------------------------</span>
</pre><p>
And a final bonus...
</p><p>
In the 3dsound timeline, the selection is represented by a darker area.
Here's the routine I use to darken an 8-bit bitmap. The idea is to build
whatever lookup tables you want which will perform the color change you
need, in this case darkening by 25%.
</p><p>
You could easily modify the "init dark table" for any other purpose you
want, like darkening everything but bright red, or remapping an image to
gray scale, etc. This is really simple but gives you a lot of flexibility
in the graphic effects of your program.
</p><pre class="programlisting cpp">
<span class="comment">//------------------------------------------------------</span>
<span class="type">char</span> <code class="varname">dark_table_inited</code> = 0;
<span class="type">uchar</span> <code class="varname">dark_table</code>[256];
<span class="comment">//------------------------------------------------------</span>
<span class="type">void</span> <code class="function">init_dark_table</code>()
{
<span class="type">ushort</span> <code class="varname">v</code>;
<span class="type">uchar</span> <code class="varname">ov</code>;
<span class="type">rgb_color</span> <code class="varname">c</code>;
<code class="classname">BScreen</code> <code class="varname">s</code>;
for (<code class="varname">v</code> = 0; <code class="varname">v</code> &lt; 256; <code class="varname">v</code>++) {
<code class="varname">c</code> = <code class="varname">s</code>.<code class="methodname">ColorForIndex</code>(<code class="varname">v</code>);
<code class="varname">c</code>.<code class="varname">red</code> = (int)(<code class="varname">c</code>.<code class="varname">red</code>*0.75);
<code class="varname">c</code>.<code class="varname">green</code> = (int)(<code class="varname">c</code>.<code class="varname">green</code>*0.75);
<code class="varname">c</code>.<code class="varname">blue</code> = (int)(<code class="varname">c</code>.<code class="varname">blue</code>*0.75);
<code class="varname">ov</code> = <code class="varname">s</code>.<code class="methodname">IndexForColor</code>(<code class="varname">c</code>);
<code class="varname">dark_table</code>[<code class="varname">v</code>] = <code class="varname">ov</code>;
}
<code class="varname">dark_table_inited</code> = 1;
}
<span class="comment">//------------------------------------------------------</span>
<span class="type">void</span> <code class="classname">TrackViewer</code>::<code class="methodname">darken_rect</code>(<code class="classname">BRect</code> <code class="parameter">r</code>)
{
<span class="type">long</span> <code class="varname">rowbyte</code> = <code class="constant">TRACK_B_H</code>;
<span class="type">char *</span><code class="varname">base</code>;
<span class="type">long</span> <code class="varname">y1</code>,<code class="varname">y2</code>;
<span class="type">long</span> <code class="varname">x1</code>,<code class="varname">x2</code>;
<span class="type">long</span> <code class="varname">dx</code>,<code class="varname">dy</code>;
if (<code class="varname">dark_table_inited</code> == 0) {
<code class="function">init_dark_table</code>();
}
<code class="varname">y1</code> = (<span class="type">int</span>)<code class="parameter">r</code>.<code class="varname">top</code>;
<code class="varname">y2</code> = (<span class="type">int</span>)<code class="parameter">r</code>.<code class="varname">bottom</code>;
if (<code class="parameter">r</code>.<code class="varname">right</code> &lt; <code class="parameter">r</code>.<code class="varname">left</code>)
return;
if (<code class="varname">y1</code> &gt;= <code class="constant">TRACK_HEIGHT</code>)
return;
if (<code class="varname">y2</code> &lt; 0)
return;
if (<code class="varname">y1</code> &lt; 0) {
<code class="varname">y1</code> = 0;
}
if (<code class="varname">y2</code> &gt; (<code class="constant">TRACK_HEIGHT</code> - 1)) {
<code class="varname">y2</code> = <code class="constant">TRACK_HEIGHT</code> - 1;
}
<code class="varname">dy</code> = (<code class="varname">y2</code>-<code class="varname">y1</code>);
<code class="varname">x1</code> = (<span class="type">int</span>)(<code class="parameter">r</code>.<code class="varname">left</code>);
<code class="varname">x2</code> = (<span class="type">int</span>)(<code class="parameter">r</code>.<code class="varname">right</code>);
if (<code class="varname">x1</code> &gt; (<code class="constant">TRACK_WIDTH</code>))
return;
if (<code class="varname">x2</code> &lt; 0)
return;
if (<code class="varname">x1</code> &lt; 0)
<code class="varname">x1</code> = 0;
if (<code class="varname">x2</code> &gt; (<code class="constant">TRACK_WIDTH</code>))
<code class="varname">x2</code> = <code class="constant">TRACK_WIDTH</code>;
if (<code class="parameter">r</code>.<code class="varname">top</code> &gt;= 0) {
while(<code class="varname">y1</code> &lt;= <code class="varname">y2</code>) {
<code class="varname">dx</code> = <code class="varname">x2</code> - <code class="varname">x1</code>;
<code class="varname">base</code> = (<span class="type">char *</span>)<code class="varname">the_off</code>-&gt;<code class="methodname">Bits</code>() + <code class="varname">y1</code>*<code class="varname">rowbyte</code>+<code class="varname">x1</code>;
<code class="varname">y1</code>++;
while(<code class="varname">dx</code>&gt;=0) {
*<code class="varname">base</code> = <code class="varname">dark_table</code>[*<code class="varname">base</code>];
<code class="varname">base</code>++;
<code class="varname">dx</code>--;
}
}
}
}
<span class="comment">//------------------------------------------------------</span>
</pre><p>
Simple!
</p><p>
So, my final question. Would anybody like to challenge my claim of
finding the "most hostile environment for programming BeOS?"
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="DevWorkshop4-35"></a>Developers' Workshop: Simple Single-Threaded Message Handling</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Owen</span> <span class="surname">Smith</span></span></div></div></div><p>
One of the most important issues that comes up in porting apps from other
OSes is adjusting to BeOS's pervasively threaded world. In today's
article, I'll show you an easy way to make apps that you're porting
happy, as well as demonstrate a sometimes-overlooked class that you can
make good use of in your BeOS-native programs as well.
</p><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id817455"></a>The Problem</h3></div></div></div><p>
If you're familiar with Windows, Macintosh, and UNIX apps, you know that
they usually run in one thread. This main thread is responsible for all
messages involving the application, its windows, their views, and any
other message targets there might be in the application.
</p><p>
For example, 99.99% of a Windows application's time in the main thread is
spent inside the main message loop, which looks something like this:
</p><pre class="programlisting cpp">
while (::<code class="methodname">PeekMessage</code>(&amp;<code class="varname">msg</code>, 0, 0, 0, <code class="constant">PM_REMOVE</code>)) {
if (<span class="comment">/* is a quit msg */</span>) {
break;
} else {
::<code class="methodname">TranslateMessage</code>(&amp;<code class="varname">msg</code>);
::<code class="methodname">DispatchMessage</code>(&amp;<code class="varname">msg</code>);
}
}
</pre><p>
Most applications that you port assume this one-thread model of handling
data. Because in this model only one thread is handling a message at a
time, there's no need for message handling routines to access their data
in a thread-safe manner (i.e., using synchronization and locking
mechanisms).
</p><p>
Now this is actually quite similar to the BeOS way of doing things --
after all, how many ways can you write a message handling loop?—though
the actual processing loop is run automatically for you by <code class="classname">BWindow</code>,
<code class="classname">BApplication</code>, and other
<code class="classname">BLooper</code>-derived denizens. The big difference in
the BeOS, however, is that each <code class="classname">BLooper</code> runs in its own thread. So in the
BeOS, there are several threads running message loops: one for your
application, one for each window, and one for each other <code class="classname">BLooper</code>-derived
object you might have created for your own nefarious purposes. If you
scatter the message- handling routines of your ported app among these
threads, disaster is almost certain to result, as suddenly the message
handling functions will be stepping on each other's toes left and right.
</p><p>
One way to fix this is to implement synchronization and locking
mechanisms throughout the app, making sure that each message- handling
routine carefully locks the data it will be handling, and otherwise deals
with the nuances of being thrust into a tangled web of threads. Managing
this, of course, will no doubt earn you the astonishment and admiration
of your fellow BeOS porters, as well as sincere thanks from the original
authors for transforming their weekend hack into a bullet-proof,
thread-safe, and otherwise divinely inspired piece of code. But if that
application you're trying to port is over, say, 100 lines in size, this
solution quickly becomes intractable—especially when you take one of
the Great Mysteries of Porting into account: that is, the code you're
trying to port is never quite as clean or understandable as you'd like --
or need.
</p><p>
The second way is to serialize all message handling in your app through
one thread, to present apps that you're porting with the threading model
that they expect. Curiously enough, this seems to be the method of choice
around here.
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id817569"></a>The Solution</h3></div></div></div><p>
To do the latter, we need to take the messages we receive and put them
into a single queue for the message-handling thread to read and dispatch.
Enter the appropriately named target of today's discussion, the
<code class="classname">BMessageQueue</code>. The <code class="classname">BMessageQueue</code>
implements a simple, thread-safe
container of <code class="classname">BMessages</code> (the Be Book has the lowdown if you're curious).
Let's look at some of the ways we might implement our solution with
<code class="classname">BMessageQueue</code>.
</p><p>
At first blush, it seems that all we need to do is add the messages we
receive to the queue:
</p><pre class="programlisting cpp">
<code class="classname">BMessageQueue</code> <code class="varname">theQueue</code>;
<code class="classname">MyWindow</code>::<code class="methodname">MessageReceived</code>(<span class="type"><code class="classname">BMessage</code>*</span> <code class="parameter">msg</code>) {
<code class="varname">theQueue</code>.<code class="methodname">AddMessage</code>(<code class="parameter">msg</code>);
}
</pre><p>
If you read my newsletter articles from a few weeks back
"<a class="xref" href="Issue4-29.html#DevWorkshop4-29" title="Developers' Workshop: The Magic of Messages Part 1: The Sending">Developers' Workshop: The Magic of Messages Part 1: The Sending</a>" and
"<a class="xref" href="Issue4-30.html#DevWorkshop4-30" title="Developers' Workshop: The Magic of Messages Part 2: The Receiving">Developers' Workshop: The Magic of Messages Part 2: The Receiving</a>",
you'll hopefully realize that something is wrong with
this code. The problem is that <code class="varname">msg</code> gets deleted when we return from
<code class="methodname">MessageReceived()</code>, so that by the time our message-handling thread reads
the item from the queue, the message pointer will be invalid. The correct
way to do this is to detach the message from the <code class="classname">BLooper</code> first so the
<code class="classname">BLooper</code> doesn't delete them behind our backs when we return from
<code class="methodname">MessageReceived()</code>:
</p><pre class="programlisting cpp">
<code class="classname">MyWindow</code>::<code class="methodname">MessageReceived</code>(<span class="type"><code class="classname">BMessage</code>*</span> <code class="parameter">msg</code>) {
<code class="methodname">DetachCurrentMessage</code>();
<code class="varname">theQueue</code>.<code class="methodname">AddMessage</code>(<code class="parameter">msg</code>);
}
</pre><p>
When we do this, remember the Solemn Responsibilities that you shoulder
when you detach the message:
</p><ul class="itemizedlist"><li><p>
Thou shalt handle the message soon. (The message sender might be
waiting for you to finish with this message, so it's good etiquette to
ensure that the message sender doesn't wait longer than necessary for
you to complete your task.)
</p></li><li><p>
Thou shalt delete the message when thou art done with the message.
(Otherwise, the message sender might wait in limbo for eternity for a
reply to come.)
</p></li></ul><p>
Now we need to implement the message-processing thread to handle the
messages. It will look something like this:
</p><pre class="programlisting cpp">
<span class="type">status_t</span> <code class="function">RunMyMessageThread</code>(<span class="type">void*</span> <span class="comment">/* obj */</span>) {
while (<code class="constant">true</code>) {
<span class="comment">// Peek until we see a message and remove it</span>
while (<code class="varname">theQueue</code>.<code class="methodname">IsEmpty</code>()) <code class="function">snooze</code>(1000);
<span class="type"><code class="classname">BMessage</code>*</span> <code class="varname">msg</code> = <code class="varname">theQueue</code>.<code class="methodname">NextMessage</code>();
if (<span class="comment">/* is a quit message */</span>) {
delete <code class="varname">msg</code>; <span class="comment">// almost forgot, didn't you?</span>
break;
} else {
<span class="comment">// translate and dispatch the message</span>
delete <code class="varname">msg</code>;
}
}
return <code class="constant">B_OK</code>;
}
</pre><p>
Alternatively, to avoid actively polling the queue in our peek message,
we can use a semaphore to signal the message thread when there are
messages in the queue. In this case, our thread will only wake up when
there are messages to handle:
</p><pre class="programlisting cpp">
<span class="comment">// semaphore is created with an initial count of 0
// the count indicates the number of messages in the queue</span>
<span class="type">sem_id</span> <code class="varname">queueHasMessages</code>;
<code class="classname">BMessageQueue</code> <code class="varname">theQueue</code>;
<code class="classname">MyWindow</code>::<code class="methodname">MessageReceived</code>(<span class="type"><code class="classname">BMessage</code>*</span> <code class="parameter">msg</code>) {
<code class="methodname">DetachCurrentMessage</code>();
<code class="varname">theQueue</code>.<code class="methodname">AddMessage</code>(<code class="parameter">msg</code>);
<code class="function">release_sem</code>(<code class="varname">queueHasMessages</code>); <span class="comment">// signals thread to
// wake up</span>
}
<span class="type">status_t</span> <code class="function">RunMyMessageThread</code>(<span class="type">void*</span> <span class="comment">/* obj */</span>) {
while (<code class="constant">true</code>) {
<span class="comment">// Peek until we see a message and remove it</span>
<code class="function">acquire_sem</code>(<code class="varname">queueHasMessages</code>);
<span class="type"><code class="classname">BMessage</code>*</span> <code class="varname">msg</code> = <code class="varname">theQueue</code>.<code class="methodname">NextMessage</code>();
if (<span class="comment">/* is a quit message */</span>) {
delete <code class="varname">msg</code>;
break;
} else {
<span class="comment">// translate and dispatch the message</span>
delete <code class="varname">msg</code>;
}
}
return <code class="constant">B_OK</code>;
}
</pre><p>
For giggles, you can extend this approach to have multiple threads
reading from the message queue—for example, if you want to farm out a
single list of tasks among a number of worker threads.
</p></div><p>
I hope these simple snippets will give you even more ideas about how you
can distribute message handling responsibilities in your application in a
flexible and simple way.
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="BitByBit4-35"></a>Bit By Bit: BFilePanels and Saving Files</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Stephen</span> <span class="surname">Beaulieu</span></span></div></div></div><p>
In this installment, Rephrase becomes a usable basic plain text editor
with the introduction of file open and save capabilities.
</p><p>
You'll find this version of Rephrase at:
</p><p>
&lt;ftp://ftp.be.com/pub/samples/tutorials/rephrase/rephrase0.1d4.zip&gt;
</p><p>
Rephrase 0.1d4<br />
New Features<br />
Create new phrases.<br />
Open existing phrases.<br />
Save phrases.
</p><p>
This version of Rephrase introduces too many programming concepts to
cover in one article. Therefore, next week's article will not introduce
any new sample code.
</p><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id818093"></a>Programming Concepts</h3></div></div></div><p>
<code class="classname">BFilePanel</code>s provide windows that let a user navigate a file system to
open one or more files or save a file at a specific location. A
<code class="classname">BFilePanel</code> can either open or save files, but this flavor must be set
when it is created.
</p><p>
All file panels present the user with a navigable view of the file
system. The panel's default button sends a message to a specified target
that contains identifiers for the file or files to open or save. By
default, the target is the <code class="classname">BApplication</code> object, but a different object
can be set through the constructor or <code class="methodname">SetMessage()</code>. The messages sent
vary depending on the flavor of the panel.
</p><p>
Open panels send a message containing all the entry refs selected by the
user. Save panels send a message with the <span class="type">entry_ref</span> of the directory
selected by the user and the name of the file to create.
</p><p>
Open panel messages have a default <code class="varname">what</code> code of
<code class="constant">B_REFS_RECEIVED</code>. Save
panels have a default code of <code class="constant">B_SAVE_REQUESTED</code>. An application can
specify a different message for the panel to send through the constructor
or <code class="methodname">SetMessage()</code>. The file panel information is added to this new message.
</p><p>
Both save and open file panels also send <code class="constant">B_CANCEL</code> messages whenever the
panel is closed, has its cancel button pressed, or is hidden. This means
that when a user selects the open or save button, two messages are sent:
first, the save or open message, and then the <code class="constant">B_CANCEL</code> message to inform
the target that the panel is no longer visible.
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id818181"></a>Implementation details:</h3></div></div></div><div class="orderedlist"><ol><li><p>
Don't build a file panel until it is requested. This saves
resources and time.
[<code class="filename">pDisplay.cpp</code>:361-367 &amp; <code class="filename">Rephrase.cpp</code>:125-129]
</p></li><li><p>
Do not delete commonly used <code class="classname">BFilePanel</code>s, as they can take time to
create. Instead, hide them and show them again when needed. In
addition, the file panel will remember its last browsed directory.
</p></li><li><p>
There is only one open file panel for Rephrase. Opening new display
windows is handled in the app. [<code class="filename">Rephrase.h</code>:24]
</p></li><li><p>
Each phrase display window has its own save panel, as each phrase
might need to be saved to a different directory. Also, synchronizing
access to one save panel from many documents would be very
complicated. With independent save panels, each panel can target a
different phrase display window.
[<code class="filename">pDisplay.h</code>:29]
</p></li><li><p>
With the introduction of custom message types for opening and
saving phrases, it's now necessary to override <code class="methodname">MessageReceived()</code> in
the <code class="classname">Rephrase</code> and <code class="classname">pDisplay</code> classes.
</p><p>
There are three main guidelines when overriding <code class="methodname">MessageReceived()</code>:
</p><ul class="itemizedlist"><li><p>
Use a switch on the <code class="varname">what</code> field of the <code class="classname">BMessage</code> to specify the
flow of code.
</p></li><li><p>
Either call functions or wrap each case in brackets to avoid
warnings and errors about "jumping past initializers."
</p></li><li><p>
Always send unrecognized messages to your superclass in the
default case of the switch statement.
[<code class="filename">pDisplay.cpp</code>:164-202 &amp; <code class="filename">Rephrase.cpp</code>:111-151]
</p></li></ul></li><li><p>
The New menu item sends a <code class="constant">NEW_PHRASE</code> message to the app, which then
creates and shows an empty display window.
[<code class="filename">Rephrase.cpp</code>:116-121]
</p></li><li><p>
The Open menu item sends an <code class="constant">OPEN_PHRASE</code> message to the app. In
response the app will show the open panel, creating it if necessary.
The file panel sends a message that is handled in <code class="methodname">RefsReceived()</code>.
[<code class="filename">Rephrase.cpp</code>:122-136]
</p></li><li><p>
To save a phrase to a file, the file's location must be specified.
This happens in one of two ways: either the phrase display window is
opened with a given <span class="type">entry_ref</span> or the <span class="type">entry_ref</span>
is specified in a save panel.
</p><p>
The save panel is shown if an entry ref has never been set for the
file, or if the SaveAs item is selected to save the contents to a new
location.
</p></li><li><p>
<code class="methodname">pDisplay::SaveTo()</code> handles all the work. Its sole argument is a
<span class="type"><code class="classname">BEntry</code>*</span>. If the argument is
<code class="constant">NULL</code>, the save panel is shown so the user
can specify a location for the file.
</p><p>
Otherwise, a <code class="classname">BFile</code> object is created and told to create the actual
file if it does not already exist. Then the contents of the phrase are
written to the file, and the size of the file is set accordingly. A
<code class="classname">BNodeInfo</code> object is created so that new files can get their mime type
set appropriately.
</p><p>
Finally the window title is set to the name of the file and the entry
ref is cached in the window for future use.
[<code class="filename">pDisplay.cpp</code>:352-433]
</p></li><li><p>
The Save menu item sends a <code class="constant">SAVE_PHRASE</code> message to the phrase
window. In response, <code class="methodname">pDisplay::Save()</code> checks to see if the cached
<span class="type">entry_ref</span> has been set. If so, it creates a <code class="classname">BEntry</code> object and passes
it to <code class="methodname">SaveTo()</code>. If not, it passes <code class="constant">NULL</code>
so that the save panel can be shown.
[<code class="filename">pDisplay.cpp</code>:334-350]
</p></li><li><p>
The SaveAs menu item sends a <code class="constant">SAVE_PHRASE_AS</code> message to the
window. In response, <code class="methodname">pDisplay::SaveTo(NULL)</code> is called, bringing up
the save panel to specify the new file location.
[<code class="filename">pDisplay.cpp</code>:174-178]
</p></li><li><p>
<code class="classname">pDisplay</code> no longer calls <code class="methodname">Show()</code> in the constructor. As
<code class="classname">BFilePanel</code>s need to have <code class="methodname">Show()</code>
called on them, it made sense to have
consistency with all window-related classes.
</p></li><li><p>
<code class="methodname">pDisplay::TargetMenuItems()</code> specifies appropriate targets for the
menu items. The New, Open, About, and Quit items target the
application, while the items in the Edit menu target the text view.
[<code class="filename">pDisplay.cpp</code>:277-303]
</p></li><li><p>
<code class="methodname">BTextView::TextRect()</code> returns a special rectangle. The sides and
top of the rectangle are set from a rectangle passed in the
constructor or <code class="methodname">SetTextRect()</code>. The bottom value is set from the height
of the text in the text view. Accordingly, the text rect could be
much taller or shorter than the rect specified in <code class="methodname">SetTextRect()</code>.
</p></li><li><p>
The <code class="classname">BString</code> utility class is used as a
replacement for <span class="type">char*</span>s
throughout much of Rephrase. The class provides type-safe and easy
<code class="function">sprintf()</code> functionality through the operator&lt;&lt; and operator+=.
[<code class="filename">pDisplay.cpp</code>:394-398]
</p></li></ol></div></div><p>
There is considerably more going on in this week's sample code. This
extra code deals with properly quitting the application, and will be
covered in detail next week.
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Gassee4-35"></a>Chips Questions</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Jean-Louis</span> <span class="surname">Gassée</span></span></div></div></div><p>
Now that IBM has announced the availability of a PPC motherboard, why
doesn't Be jump on the bandwagon and commit to this hardware? My
apologies for not doing full justice to the number, variations, or
eloquence of the messages sent to info@be.com or directly to me. You get
the idea, though. Something is happening in the PPC world and Be ought to
support it. One correspondent expressed it as a brutal Wall Street
reduction: "I would even be willing to bet that this would cause the most
dramatic rise in Be stock since the IPO."
</p><p>
The stock price is not a topic for me to comment on, but the sentiment
regarding the PowerPC needs to be addressed (immediately, which is why
I've postponed the next installment of the IPO story, "Going Public: Part
II"). The processor itself is a fine one, as the benchmarks prove. We've
had versions of BeOS on the PowerPC since 1994, including our current 4.5
release (whose package clearly states "For x86 and PowerPC"). So, what
we're dealing with is not really a processor dilemma. Is it then a
question not of chips but of chipsets? Setting aside a possible
oversimplification for the moment, this turns out to be a better lead to
the real issues.
</p><p>
Look at the evolution of PC motherboards. More and more functions get
integrated, as opposed to implemented on plug-in cards. In the process,
the "chipset," the chip or chips interposed between the faster and faster
CPU and slower devices such as the PCI bus, memory, frame buffers and the
like, become richer and richer mediators. The complexity of the better
chipsets now rivals or exceeds that of the CPU itself.
</p><p>
The operating system, in turn, needs to support these mediating devices;
otherwise, the hardware, or the OS, or both aren't going anywhere. Take
Silicon Graphics' recent decision to de-emphasize their low-end
workstations based on Intel processors and Windows NT. These workstations
required two proprietary objects, a physical one and a logical one. On
the physical side, Silicon Graphics decided to use ASICs to express their
knowledge of the application space. Together, these Application Specific
Integrated Circuits constituted a "chipset" providing high-performance
I/O for graphics, video, and audio applications in SGI's heritage. The
idea was to achieve a level of performance higher than that offered by
the standard PC clone organ bank. This, in turn, required a logical
object, NT software, drivers and, perhaps, modifications to NT itself.
Meanwhile, the relentless clone industry continued to drive the price of
standard organs down and the performance up. The clone drive reached a
point where the SGI solution became less likely to achieve its original
goals.
</p><p>
If that example isn't enough, recall recent announcements regarding
high-end chipsets for servers. Two camps were ready to make war over a
standard, one led by Intel and, if memory serves, the other by Compaq.
But the cost of the conflict and the benefits of having only one standard
led to peace. We can now expect organ bank support for eight-way
multiprocessing, coming soon from your favorite motherboard supplier.
</p><p>
To return to PowerPC hardware, we need to know more about chipsets that
support the PowerPC. Who builds them, how competitive are they, which I/O
devices are supported, how is the technical documentation accessed, who
fixes bugs in the product and the documentation? As far as the IBM PPC
hardware is concerned, other questions arise. Where can I buy it and
where can I get it fixed, for instance? As answers emerge, it will be
easy for us to make a decision.
</p><p>
That said, I still haven't answered the Apple question. If Apple is
selling respectable quantities of PowerPC hardware, how come we don't
support the latest G3 and G4 machines? As I said above, it boils down to
a chipset issue. Intel doesn't have an operating system to defend. In
their best interest, they profess to be "OS-agnostic," the more options,
the better. In Apple's case, it's different. They own and operate an OS
and, like Microsoft, see no reason to help a competitor. Linux provides
access to classical Unix applications and, therefore, is little
competition for Apple's multimedia heritage. The same can't be said of
BeOS, and I can see the logic in Apple's decision not to help us with
access to chipset technical data for a G3/G4 BeOS port.
</p><p>
Some have suggested that we look into the Linux sources for such data.
Perhaps, but I see little reason to open ourselves to possible
accusations of reverse-engineering. We're welcome on x-86 hardware, we're
not welcome on Apple G3/G4. We respect the logic and that settles it for
us.
</p></div></div><div id="footer"><hr /><div id="footerT">Prev: <a href="Issue4-34.html">Issue 4-34, August 25, 1999</a>  Up: <a href="volume4.html">Volume 4: 1999</a>  Next: <a href="Issue4-36.html">Issue 4-36, September 8, 1999</a> </div><div id="footerB"><div id="footerBL"><a href="Issue4-34.html" title="Issue 4-34, August 25, 1999"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a href="volume4.html" title="Volume 4: 1999"><img src="./images/navigation/up.png" alt="Up" /></a> <a href="Issue4-36.html" title="Issue 4-36, September 8, 1999"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="footerBR"><div><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div></div><div id="footerBC"><a href="http://www.access-company.com/home.html" title="ACCESS Co."><img alt="Access Company" src="./images/access_logo.png" /></a></div></div></div><div id="licenseFooter"><div id="licenseFooterBL"><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by-nc-nd/3.0/88x31.png" /></a></div><div id="licenseFooterBR"><a href="./LegalNotice.html">Legal Notice</a></div><div id="licenseFooterBC"><span id="licenseText">This work is licensed under a
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative
Commons Attribution-Non commercial-No Derivative Works 3.0 License</a>.</span></div></div></body></html>