One of the many good things in BeOS Release 3 was the addition of system-wide translation services through the Translation Kit. While translation between different data formats was previously available in the form of a third-party library named Datatypes, having a Kit in the BeOS makes it easier to use and install, because you can assume it's always there. The Translation Kit will load any old-style Datatypes add-ons, but the interface used by Datatypes is deprecated.
The actual work of translating data in the Translation Kit is performed by add-ons known as translators. This article explains what a translator add-on must do to be used by the Translation Kit, and what it can do to be a well-behaved citizen in the world of data format conversions.
But first, the code! The archive can be found at:
ftp://ftp.be.com/pub/samples/translation_kit/ppm.zip
The purpose of our translator is to allow applications to read and write the PPM bitmap file format. I chose PPM because it is a format that is fairly simple to understand, while having enough variation to illustrate how to configure a translator add-on. It is also a fairly popular format for UNIX-style image processing tools.
For translation to work, there has to be some common ground between the
translators, and the applications using them. For bitmap graphics, this
common ground is found in the B_TRANSLATOR_BITMAP
format, a format
designed to be easily readable and writable to BBitmap
system objects.
The format of a B_TRANSLATOR_BITMAP
formatted file is simple:
First, there is the file header, which consists of a struct:
struct TranslatorBitmap { uint32magic
; /* B_TRANSLATOR_BITMAP */BRect
bounds
; uint32rowBytes
; color_spacecolors
; uint32dataSize
; };
As you can see, all elements of this struct are 32 bytes in size (except
for the BRect
, but all elements in a
BRect
are 4 bytes in size) so there
should be no alignment problems when reading/writing this struct on
different platforms. However, the byte order needs to be well defined,
and since Datatypes was around long before the x86 port of BeOS, the
well-defined byte order of the TranslatorBitmap struct is big-endian.
The magic
field should be set to the
B_TRANSLATOR_BITMAP
constant,
swapped to big-endian if necessary.
The bounds
field should be set to the
Bounds()
that a BBitmap
system object would use to contain the image. Note that
Bounds()
.right
is ONE LESS than
the width of the image in pixels, because the 0-th pixel counts as one
pixel. Again, you need to swap the BRect
members as
necessary.
For rowBytes
, see below.
colors
is one of the values defined in
GraphicsDefs.h
which describe
various ways you can interpret the raw pixel data. In R4, there will be
more values defined for a color_space, although not all values work if
you use DrawBitmap()
to draw such a bitmap to the screen. In the sample
source, we have cribbed the definitions for B_CMYK32
and relatives from
R4 so that we can illustrate how to convert between color spaces.
dataSize
, lastly, should tell how much pixel data follows the header,
but the size of the header (32 bytes) does not count. This should always
be set as follows:
header
.dataSize
= (header
.bounds
.Width
()+1)*header
.rowBytes
Again, be careful about byte-swapping.
After this struct the actual data of the bitmap follows directly, from
left to right, top to bottom, padded to rowBytes
bytes per scanline.
rowBytes
is typically the smallest multiple of four bytes that will fit
the width of the bitmap of whole pixels across.
The general rule with regards to byte-swapping is to swap only when you
need to read or write data, and keep it in the native format internally.
Doing this ensures that you can easily access the values of the header.
For instance, if you were to write a BBitmap
out in the
B_TRANSLATOR_FORMAT
format, here's how you could do it:
status_tWriteBitmap
(BBitmap
*map
,BDataIO
*out
) {TranslatorBitmap
header
; /* prepare header */header
.magic
=B_TRANSLATOR_BITMAP
;header
.bounds
=map
->Bounds
();header
.rowBytes
=map
->BytesPerRow
();header
.colors
=map
->ColorSpace
();header
.dataSize
=header
.rowBytes
*(header
.bounds
.Width
()+1); /* swap header */header
.magic
=B_HOST_TO_BENDIAN_INT32
(header
.magic
);header
.bounds
.left
=B_HOST_TO_BENDIAN_FLOAT
(header
.bounds
.left
);header
.bounds
.top
=B_HOST_TO_BENDIAN_FLOAT
(header
.bounds
.top
);header
.bounds
.right
=B_HOST_TO_BENDIAN_FLOAT
(header
.bounds
.right
);header
.bounds
.bottom
=B_HOST_TO_BENDIAN_FLOAT
(header
.bounds
.bottom
);header
.rowBytes
=B_HOST_TO_BENDIAN_FLOAT
(header
.rowBytes
);header
.colors
= (color_space)B_HOST_TO_BENDIAN_FLOAT
(header
.colors
);header
.dataSize
=B_HOST_TO_BENDIAN_FLOAT
(header
.dataSize
); /* write header */ status_terr
=out
->Write
(&header
, sizeof(header
)); /* write data */ if (err
== sizeof(header
)) {err
=out
->Write
(map
->Bits
(),B_BENDIAN_TO_HOST_INT32
(header
.dataSize
)); if (err
==B_BENDIAN_TO_HOST_INT32
(header
.dataSize
)) {err
=B_OK
; } } return (err
>B_OK
) ?B_IO_ERROR
:err
; }
I have sloppily been saying "file format" above; the truth is
that any BPositionIO
object can be used by a
translator, and as long as you can Seek()
and
SetSize()
and Read()
and
Write()
it, it needn't be a
BFile
proper. It can be one of the system classes
BMallocIO
or BMemoryIO
, or it
can be your own class that knows how to read and write data to some special
storage you're using. This is used by the system class
BBitmapStream
, which knows how to present a
BBitmap
as a "stream" of data.
Now, your job as a bitmap image translator is to read data in your
"special" file format from the input stream, and write it to the output
stream in the "standard" bitmap format as explained above. You should
also be capable of doing the reverse: reading data in the "standard"
bitmap format, and writing it out in your special format. This
reading/writing is done in the exported Translate()
function.
Translate()
is passed an input and output stream, type information that a
previous call to Identify()
returned, possibly a
BMessage
containing some
configuration information and out-of-bounds information, and a requested
output format type. This type is a four-letter type code as found in and
other system headers, and the specific value is taken from your
outputFormats[]
array or the return data from
Identify()
. If there is no
type code defined for the format you're dealing with, you have to make
one up. When you do, remember that Be reserves all type codes that
consist solely of upper case letters, digits, the underscore character
and space. Your best bet is to use lowercase letters in your own type
codes.
There are standard formats for some other kinds of data besides bitmap
images. You can find them in
TranslatorFormats.h
, and they are also
described in the Translation Kit chapter of the online Be Book.
There are some things that you need to get the Translation Kit to the
point where it calls your Translate()
function. There are many
translators installed in a typical user's system, so how does it know
which translator to use? Typically, a translator is selected in one of
two ways:
1) An application that implements an "export" menu item, such as the
Becasso paint program, or the R4 version of ShowImage, calls on the
Translation Kit to list all available translators, and to select those
that say that they can translate from the B_TRANSLATOR_BITMAP
format to
some other format. It then lets the user choose one of these translators
using some UI (a dialog or menu, typically) and tell the Translation Kit
to especially use the translator selected.
For this to work, your translator needs to tell the world what formats it
can read and write. It does so in the inputFormats[]
and outputFormats[]
arrays. These are arrays of struct translation_format, terminated by a
format with all 0 values. While exporting these arrays is called
"optional" in the documentation, applications that want to perform an
export will not know about your translator unless it exports these arrays.
Also note that there is no way to specify that only certain combinations of
input and output file formats are good. Once you declare some input formats
and some output formats, any combination of them may be used by the
Translation Kit, including, in some degenerate cases, translating the SAME
format (i e B_TRANSLATOR_BITMAP
to
B_TRANSLATOR_BITMAP
). You decide how to best deal with
this situation; just copying >from input to output is acceptable,
although if your translator can also do some other tricks (like the color
space conversion of PPMTranslator) you might
want to do that even on same-format translations.
2) An application that accepts "any file" and then uses the
Translation Kit to figure out what it was will cause your
Identify()
function to be called. The role of your
Identify()
function is to look at the beginning of
the file and figure out if it is in one of the formats you know how to
handle or not. Note that Identify() is often called before
Translate()
, even if the application selects your
translator specifically, so you have to do a good job here.
Because the BPositionIO
passed for input may have
some special meaning, such as reading from a network socket, you should not
read more data than you need to make an educated guess as to the format of
data you're passed. Similarly, calling Size()
or
Seek()
relative to the end-of-file of the
BPositionIO
might be an expensive operation that
causes the entire file to be downloaded to disk before it returns, so it
should be avoided in your Identify()
function. If
not, you might not recognize the format, and then the user wasted an hour
on a 28.8 kbps download just to get nothing useful out. Also, some
applications use the Translation Kit only to identify what something is;
they don't actually Translate()
it. Wasting time
getting to the end of the file is then doubly pointless.
There are some additional required data items you need to export from
your translator for the Translation Kit to use it. They tell the world
your translator's name, some information about it, and the version. If
there are two translators with the same name but different versions
installed, the Translation Kit may choose to use only the latest version,
for instance. Thus, you should make sure that you always bump the version
number when releasing a new version of your translator, and that you
never change your translator's name (as seen in
translatorName[]
) once
it's set.
translatorInfo[]
is your personal soap box, and is a great place to put
shareware notices, copyright information, URLs, or secret cabal messages.
Except that then they wouldn't be secret anymore.
There are three more optional functions that you may choose to export, even though your translator will work and be used by the Translation Kit without them.
MakeConfig()
allows you to create a
BView
(to which you can add other
BView
s such as BCheckBox
es
and BMenuField
s) that a client application can add
to a window and display on screen. The purpose of this view should be to
twiddle whatever tweakable parameters your translator has, and the View
should remember these changes for later uses. You can see this implemented
in the PPMTranslator as the struct
ppm_settings variable g_settings
, and the
PrefsLoader
class instance
g_prefs_loader
.
GetConfigMessage()
should return a
"snapshot" of the current settings in the message passed to it.
An application can pass a copy of this "snapshot" message data to
a later call to Translate()
, and your translator
should then use whatever settings are kept in that message rather than the
defaults. Similarly, an application can pass a copy of the data in this
message to MakeConfig()
to have the view
preconfigured to the settings stored in that message rather than the
current defaults (although the translator is allowed to change the defaults
to what's in the message, as done in
PPMTranslator).
These two functions together make it possible to create an application which can present a UI for choosing a translator, to configure that translator, and later to use that specific translator/configuration pair to actually perform a translation. Great for automated batch conversions, for instance!
For more detailed information on the functions used by the Translation Kit, look at the Translation Kit chapter of the Be Book, the section on writing a Translator add-on.
The last optional function is main()
. On the BeOS, there really isn't
any difference between shared libraries, add-ons, and applications,
except in the way they're used and what you call them. You can load an
application as an add-on, or launch a shared library, providing that the
executable in question exports the right functions. To be an application,
all you have to do is to export a symbol named main()
.
PPMTranslator takes advantage of this schizophrenia to do something
useful when double-clicked—it runs its own control panel by calling
its own MakeConfig()
function and adding the resultant View to a window,
and then quits when the window is closed. I recommend that all translator
add-ons do the same thing; that gives a user an easy way of setting the
defaults for use by applications that don't display translator user
interfaces, and users also get something useful out of double-clicking
what might be an unknown executable found on their disk.
Once your translator is debugged and ready to ship, you only need to make sure it gets installed where the Translation Kit will find it. By default, the Translation Kit will look in the following three places for Translator add-ons:
/boot/home/config/add-ons/Translators/ /boot/beos/system/add-ons/Translators/—reserved for Be /boot/home/config/add-ons/Datatypes/ —for old Datatypes
However, the user can change this behaviour by setting the environment
variable TRANSLATORS
. Users who do this are considered power users, so
making sure your translator gets installed in
~/config/add-ons/Translators/
by default is the right thing to do.
Before I end this article, I want to explain a few things about the code included with this article.
First, there is downloading and installing the code. Just get it from the
URL above, put it where you usually put sample source code, and un-zip it
(or let the Expand-o-matic do it for you). Then, in a Terminal window,
"cd" to the newly un-zipped folder, and type make
install
to build and install the
PPMTranslator and the
translate
command-line tool. Documentation for the use
of translate
is scarce, but you have the source, so you
should be able to figure it out from there.
PPMTranslator should be doing most things "right" and thus be suitable as sample source. If you find something you don't like or think might be a bug, I'd be interested in hearing about it, and fixing the archive.
The utilities in colorspace.cpp
are intended as a quick way to get the
job done when you need to output data in some color_space format other
than what you have. They are not intended as a high-quality color
convolution or separation package. Specifically, the conversion to
grayscale is sub par, and the conversion to/from CMY(K), while correct,
assumes that you're using perfect inks on perfect paper. I wish!
If you read through the sources and conclude that Release 4 will define new
values for the color_space enum for color spaces not
previously defined in , you are correct. However, there is one caveat:
while using this enum to communicate color space information is convenient,
not all applications or classes will support all color spaces. Drawing a
BBitmap
in the B_CMYK32
color
space to a BView
will not work; nor can you draw
into a BBitmap
with a color space of
B_YCrCb_422
. Still, having names for these spaces is
better than a complete vacuum.
What are you waiting for? The source is there to explore, and the world is waiting for your translators! Shoo!
Your programmers work 24-hour days to get your product ready for domestic sales. But, although 50% or more of your revenue is on the line, you don't think to ask them to test the product under the German or Japanese version of the operating system. It's only after your product starts appearing on shelves at CompUSA that the Vice President of Worldwide Sales asks you to demonstrate this hot application running under the Japanese operating system for a group of high-ranking executives from Tokyo. After the fourth crash and reboot, you realize that something isn't right with your product. Your VP apologizes, then says goodbye to the high- ranking executives and to 30% of worldwide revenue.
For most development houses with an international presence, international sales account for upwards of 40% of total revenue. These revenues, however, can be severely reduced or eliminated for any software release when products aren't engineered for sales in international markets. In effect, your engineering department determines your global sales strategy, which may well end up defeated by or suffering from long and expensive localization processes because software products have been written with only a single target language in mind.
Before localizing your software, you need to internationalize it. Internationalization is the process of creating a single code tree that is easily localized to multiple languages. Before spec, during development, and through final code freeze, your engineers should be well-versed in writing international software. Here are five of the most common pitfalls that cripple or defeat the international value of software products.
English letters are usually presented as single-byte characters. Japanese, Chinese, and Korean characters are double-byte. Some text can be represented vertically. In the Middle East, some languages flow from right to left. All platforms have API calls to display all major languages, including US English. A program can automatically display date, time, and other culture standards by querying the operating system. Make sure your programmers learn and use these APIs wherever characters are displayed.
Some components may not be localizable. A third-party vendor may not license a component for international distribution, or may demand additional licensing requirements and fees to localize it for you. Before a component is even tested for use in a product, require a sign-off for worldwide rights and compatibility with all international versions of the OS your company targets.
Your programmers always keep pieces of text, pictures, and sounds in a separate, editable resource file, right? If resources aren't kept separate from the start, your programmers will either have to re-engineer later, or comb through your source code every time you localize into another language. Separate resources are relatively easy to edit, reducing engineering costs considerably, or allowing you to outsource localization entirely.
Almost every SDK starts as English-only. Even Sun's Java Development Kit only recently added true international support. If your product is dependent on a third-party SDK, ensure that it is either compatible with targeted international operating systems, or that you are absolutely sure that international versions will be available by the time you are ready to localize.
Tech writers and help authors like to adapt as much as possible to the platform they are writing for, and add improvements with incremental releases. Usually, help systems, as well as electronic documentation systems and players, do not exist on all platforms and language versions, increasing development costs for these systems. Every new or different word in the documentation has to be translated, copy-edited, and re-published. Therefore, it is practical and cost-effective to standardize on cross-platform, open systems, and to enforce a company policy of standardized product terminology.
International success depends not only on the savvy of your business development team, but on a cooperative and proactive understanding of how to create an internationalized product. Being aware of the pitfalls and integrating strategies into your product specifications will allow you to realize your global revenue potential.
Lynn Fredricks is the President of Proactive International, an international business development company that establishes worldwide distribution networks for software developers. Proactive International specializes in high-profile distribution between North America and Japan, as well as providing product and business analysis for large international corporations. Additional information can be found at http://www.proactive-intl.com/.
A while back, Stephen Beaulieu mentioned that DTS has divided support responsibilities for the various areas which make up the BeOS, based on familiarity, experience, and preference. The areas which fell towards me include OpenGL®, POSIX, Replicants, hardware, and printing. When I think about what I'd like to write for my Newsletter articles, I look into the questions which have come my way recently to see what people are asking about. This article was going to be on OpenGL®, but people asked about printing last week, so that's what we'll look at. OpenGL® will have to wait until my next article!
Printing is a great topic, because it produces a physical representation of your work. It's very validating. You can hang your hardcopy on your office wall, show it to your friends, and just generally impress people with it. Portable, high-contrast displays are wonderful, but printing will always be valuable. As I once heard it put so eloquently, the paperless office is as much a myth as the paperless bathroom. (There's a very subtle double entendre in there, "for the connoisseur" as Jean-Louis might say.)
Benoît Schillings wrote an excellent article...
Be Engineering Insights: Proper Printing
...a short while back which explained how to get up and running with printing on the BeOS. I want to expand on that article just a little, showing a couple of techniques which you may want to incorporate into your own applications. The code we'll be adding printing to is none-other-than Dynadraw, the perfect vehicle for showing off printing. You can grab the source code from:
ftp://ftp.be.com/pub/samples/interface_kit/dynadraw3.zip
In printing out Dynadraw views, it would be nice to have two modes: one in which one pixel on the screen corresponds to one typographical point on the page, and another where the entire view is scaled to fit on a single page. Correspondingly, under the menu, you'll find the item which has a check next to it. The check implies that we'll scale the view to the page, and the absence of the check implies that we'll produce "life size" output.
The DDWindow
class manages the printing, and is the target of the two new
messages, PRINT_REQ
and SCALE_PAGE
.
SCALE_PAGE
notifies the FilterView
that we're toggling printing modes. PRINT_REQ
calls the imaginatively
named DoPrint()
, which sets up the print job, requests the view to draw
in the correct position, spools pages, and commits the job.
The most important thing to do when creating a print job is to set the
print settings. This is usually done by making a call to
BPrintJob
::ConfigPage()
. These settings are stored
in a BMessage
, which you can get a pointer to by
calling BMessage
::Settings()
. It is extremely
instructive to view the contents of this message when investigating or
debugging by calling PrintToStream()
on the
message—try uncommenting these lines in
DoPrint()
and then running the application from a
Terminal window:
printf
("printSettings message:\n"printSettings
->PrintToStream
();
Next we calculate how wide and how long the printout will be. We do this by taking the ratio of our view width to our printable rectangle width, and rounding up with the ceiling function. The total number of sheets, therefore, is horizontal sheets multiplied by vertical sheets. We then loop through the pages, offsetting the current page rectangle to the top of the next page.
With the current page rectangle lined up correctly, we call
BPrintJob
::DrawView()
, which calls
FilterView
::Draw()
with the rectangle
described by curPageRect
, and positions the output at (0,0) on the page.
Having drawn, we add the page to the spool file, check that we haven't
been interrupted, and continue the loop. Finally, we commit the job, and
send the spool file to the printer.
The FilterView
::Draw()
function needs to be modified only slightly. If
the view is being printed and the user has selected , then
we determine a scaling factor and apply it. We determined the scaling
factor here by finding the horizontal scaling factor and the vertical
scaling factor, and taking the smaller of the two. We call SetScale()
with this argument after reducing it by an epsilon, to make it more
attractive on the page. (Note that SetScale()
takes a double, with 1.0 ==
100%, that is, full size.)
The BView
::IsPrinting()
function is critical to
any application which does printing. It allows the
Draw()
function to modify its behavior for the
printed page versus the screen. Any "screen-only" code you have
in your draw function should be wrapped in an "if
(!
)..." check.
IsPrinting()
As you can see, the BPrintJob
class and the
BView
class work hand-in-hand
to allow you to add quick and easy printing to your applications. Read
Benoît's article, read about BPrintJob
in the
Be Book, and try it yourself! Happy printing!
This is not about making a m?salliance of respected professionals and confidence men in the same column. No, today's topic is an attempt to balance two opposing views of an issue. That is, the good and bad sides of a hypothetical venture fund whose sole purpose would be investing in Be developers.
Actually, this isn't so hypothetical. This week's choice of topic was triggered by reactions to a mention on our site of Marco Bernasconi's BeFund:
http://www.befund.com/
Marco, a long-time friend of Be, is based in Switzerland, and he *is* the fund.
We're all grateful for Marco's role as an "angel" to Be developers and, as the celestial label implies, this is not a classical Silicon Valley venture fund. What many correspondents have asked, however, is whether there should be a standard venture fund for BeOS-centric companies. I'll try to reproduce their arguments for and against, taking full responsibility for whatever distortions I might introduce in the process.
On the pro side, several readers point to the keiretsu approach adopted by Kleiner Perkins in convincing other investors to join them in investments supporting the Go/Eo platform. More recently, Kleiner Perkins took a similar course in leading the creation of a Java Fund.
As is now well understood, the reasoning behind this kind of venture investment is that a new platform needs a critical mass of symbionts such as software or hardware developers; the platform support fund provides capital to these helper companies.
If the platform "ignites," all investors, whether in the platform company or in third-party companies, are in a good position to profit handsomely for being in before success became retroactively obvious. If critical mass isn't achieved, well, they tried, as manly venture investors are supposed to do.
Critics of the idea call it "dirigiste" (that is, interventionist), an affront to the way things are done in free-market heaven. Essentially, they say, if a start-up offers a money-making opportunity, it will get financed. There is so much capital available right now that any good team with a good business plan will attract funding.
In other words, if the free market doesn't fund BeOS developers, listen to what investors are saying: the business plan won't work. Perhaps it's the team, or the product; more likely there isn't enough confidence that the platform will reach critical mass and reward investors accordingly.
Furthermore, opponents of the idea point out, the Go/Eo keiretsu didn't go anywhere, and there is doubt that Java will ever ascend to the Windows-killer platform status originally ordained for it. In other words, the dirigiste idea doesn't work, critics say, looking at my passport.
Others take a different perspective. They point out that most large companies now deploy a strategic investment fund of one kind or another. These days, one of the most visible examples is Intel, but from Adobe and AT&T to 3Com and Cisco, "everybody does it." That does not automatically make it a good idea, but one is tempted to assume these companies have a different conduit for their philanthropic activities.
In other words, all these CEOs, and their boards of directors, believe in the divine intervention of the free market for most things, but they are collectively willing to put billions in play when it comes to creating critical mass, or a self-fulfilling prophecy, rather than letting nature take its course. If memory serves, in the early eighties, a rich Apple took a 20% equity position in the new and, at the time, unproven Adobe. That investment helped the rise of the Macintosh platform and Apple made a killing of Microsoft proportions on its Adobe stake.
In fairness, the examples go back and forth, and for every success story one may find failures of Momenta proportions. There is no absolute truth in this matter of platform-support investments. There is only risk taking, and the small matter of execution.
BeDevTalk is an unmonitored discussion group in which technical information is shared by Be developers and interested parties. In this column, we summarize some of the active threads, listed by their subject lines as they appear, verbatim, in the mail.
To subscribe to BeDevTalk, visit the mailing list page on our web site: http://www.be.com/aboutbe/mailinglists.html.
Is it possible to produce a Be app by cross-compiling on another platform? The libraries are, as Ernest S. Tomlinson pointed out, "Portable Executable binaries", so some clever compiler direction should do the trick. Right?
Fred Fish pointed to a tract in the Microsoft Developer Network Library called "Learn System-Level Win32 Coding Techniques by Writing an API Spy program" that shows you "how to make all client-sharedlib calls go through some private code you supply." Thomas Hudson nominated Chris Herborth's port of SWIG
http://www.be.com/beware/Development/SWIG.html
which "analyzes C++ code and generates an interface to various scripting languages such as TCL/Tk, Python, Perl..."
There was some shouting from the "feel the pain" crowd (REAL BeOS developers use MW/BeIDE). And some listeners took the opportunity to re-open the binary format debate (ELF vs PEF/PE).
Should a BHandler
be destroyed when its
BLooper
is destroyed? Jesse Hall
sees the looper/handler relationship as similar to that of window/view.
And just as dying windows destroy their views, loopers should clean up
after themselves.
But maybe not: Matt Brubeck thinks unhitching the fate of a handler from its (current) looper makes message redirection easier, particularly when the looper is a window.
THE BE LINE: To paraphrase Peter Potrebic, the fact that a destructed looper doesn't destroy its attached handlers is not a bug, and won't change. However, the looper should (but doesn't) remove its handlers while its being destroyed. This is a bug and will be fixed.
Tinic Uro submitted a mathlib mystery spot:
printf
("%f\n",(float)rint
(8.5));printf
("%f\n",(float)rint
(9.5));
..produces '8' and '10'. Why?
Jens Kilian offered an explanation:
“This is the American way to round a number—if it's exactly halfway between integers, it is rounded to the EVEN one. The German way, which I and (presumably) you were taught, is to simply take ceil(x + 0.5), always rounding UP when halfway between numbers.”
International relations at stake, a number of listeners wrote in to
suggest using floor()
and ceil()
(or, for the latter, floor(x+.5)
)
instead of rint()
.
THE BE LINE: (From Mani Varadarajan)
“[rint()
's behavior] is correct. To prevent skewing rounding errors only
one way, it is a well-established rule that one should always round to
the even number if a number is exactly between an even and odd value
(pardon the lack of precise mathematical terminology).
If the rule were to round always up, the error would be skewed in one direction. This evens out the error.”
What's Be's Java policy? It was announced that Be had been dealt into Sun's floating crap game, but have they ante'd up? Is there a JVM on the horizon?
THE BE LINE: Be is NOT an official Java licensee. We'd love to see a JVM running on the BeOS, but we have no plans to work on one ourselves.
The windows for all instances of a multi-launch app are listed together in the Deskbar's app window list. How do you tell which windows belong to which instance of the app? Suggestions:
Map your windows to logical units. For Felix (for example), this means each window would represent a distinct server, and tab views within the window would serve the various channels on that server.
Go "docu-centric" and teach the windows to identify themselves. In the context of the previously proposed Felix example, a window would name the server & channel to which it's connected. You don't really care which instance of the app runs a particular window, all you care about is the window's target/contents.
Make your app single launch, multi-window. Why relaunch the app just to open another channel? If the app is well-written, you should ALWAYS be able to get another window.
Fine suggestions all, each with their own set of advantages and blemishes. And of the latter for the latter, comes this from Matt Brubeck:
“I have NetPositive running in workspace three. I want to open a NetPositive window in workspace one. Ideally, I should be able to go to workspace one, launch NetPositive, and have it open a new window. Currently, I have to go to workspace three, tell NetPositive to open a new window, use Workspaces to drag the window to workspace one, then go to workspace one. Bleah.”