One of the nice things we added in R4 was a more flexible drag-and-drop
protocol. Many developers have already peeked inside the BMessage
being
dragged when you grab a clipping from ShowImage in an effort to try and
figure out how it works. Because it actually involves more than one
message, and we would like all programs on BeOS to do the right thing,
I'm going to document the current protocol.
First, the code! You can find it at:
ftp://ftp.be.com/pub/samples/interface_kit/dragme.zip
The application is simple. Start it, and it shows a window with a little image in it. You can click on the image to drag it around on your screen. If you drag it into a Tracker window, the Tracker creates a clipping file that the application saves the image into. If you drag the image to the Trash can, the application closes the window, the only way it knows how to remove the image. While not the best example of intuitive user interface design, it serves the purpose of illustrating drag-and-drop pretty well.
The basic idea behind the new drag-and-drop protocol (DnD for short) is
that the initiator of the drag doesn't have to produce the data in all
formats it can possibly generate just to start dragging. Instead, it puts
into the message a list of the formats it knows how to generate using the
data being dragged. In this case, the DragView
inside
the DragMe
window
is the initiator.
The recipient of the drag message, in this case the Tracker, can then look through the message for data formats it likes and send a reply to the message back to the initiator asking for one of these formats. Currently, the Tracker uses the first format added to the list of formats, unless you hold down the Control key. In that case it gives you a list of possible formats, or the option to cancel the drag altogether. Note that DragMe provides only one format of data, while ShowImage provides all formats available through the Translation Kit.
Once the target (in this case the Tracker) chooses a format and sends a
message back to the initiator, the initiator extracts all the data it
needs from its internal data storage, and puts it either in a new message
sent back to the target, or in a newly created file specified by the
target. If the data is sent back to the target in a BMessage
, this
message is called the "data message," as opposed to the "dragged message"
which was the first message, or the "reply" sent from the target to the
initiator. That the target creates the file has to do with needing to
preserve the drop point as the icon location in the Tracker window, which
is better kept internal to the target than exposed in the API.
The nitty-gritty of setting up the drag is found inside
DragView
::MouseDown()
.
After figuring out whether we actually clicked in
the bitmap and want to drag it, we add the types of data we can provide
to the message, along with the actions we accept. The MIME type we use to
export data comes from the first Translator we find that can save bitmap
images, found and remembered in the constructor of the DragView
class.
The MIME string of data types you know how to provide should be added to
the drag message in the attribute named "be:types". The magic type
B_FILE_MIME_TYPE
means that we can create "a file"
in addition to MIME data
within a data message. It's OK to not add any other types at all, if all
you know how to do is to save files. That makes direct data exchange
between your application and other applications impossible without using
temporary files, though, so you typically want to do both.
If you use the B_FILE_MIME_TYPE
type, you
should also add the MIME types
of files you know how to create (which may be different from the types
you know how to put in data messages) into the attribute "be:filetypes".
DragView uses a copy of the content BBitmap
as the drag indicator. That
works fine because the bitmap is small. If the bitmap is larger, you
should consider just dragging an outline BRect
. You'll note that we use
the new form of DragMessage
found in R4, which allows you to specify a
drawing mode for the dragging; we use B_OP_ALPHA
because we prepare the
dragging BBitmap
specifically with alpha in mind. We also specify the
window (rather than the view) as the reply destination for the dragged
message.
The actions we know about are B_COPY_TARGET
and B_TRASH_TARGET
. The
former has the target send a request for data as reply; the latter does
not require you to send the data anywhere, but instead indicates that the
target wants you to remove the data being dragged (typically because the
user dragged to the Trash). Possible actions are added to the
be:actions
attribute of the drag message.
The be:clip_name
attribute is optional, and gives a hint to the
destination for what the file name might be if it wants to create a
clipping file. The target is free to ignore this hint and create a
clipping file with any name it wants.
In DragWindow
::MessageReceived()
, we receive the
reply from the target and dispatch on the action found in the
what
code. For B_TRASH_TARGET
, we
close the window (with a confirmation dialog for good measure). For
B_COPY_TARGET
, we figure out whether to write data to
a file (B_FILE_MIME_TYPE
) or to the message directly.
In both cases we use the Translation Kit to translate from a
BBitmapStream
to the data type found in the
DragView
constructor. We do this because it's
convenient, but the Translation Kit is entirely optional—it's not at all
required to implement the underlying dragging protocol.
So what do you have to do to be at the receiving end of this protocol? Not
much, really. You receive B_SIMPLE_DATA
messages just
like messages that have real data in them. If the
B_SIMPLE_DATA
message doesn't have a data type you
understand in it, you can look for be:types
to see if
the initiator can provide data of some type you understand. If so, you send
a reply back to the initiator (using SendReply()
)
with the type you want in be:types
(and file type in
be:filetypes
if you want a
B_FILE_MIME_TYPE
) and the what
code
set to the action you want (typically B_COPY_TARGET
).
In reply to this message, you'll receive a B MIME DATA message with the
data in it, or, if you requested a file, the initiator will save the data
to disk and then send a message back to you. DragMe
,
in this case, uses the default message error mechanism, so you should
probably not fail on error messages received in reply to file save
requests.
That's it. Now don't drag your feet; go and implement drag-and-drop in your application today!
The configuration manager, first introduced in BeOS Release 3, has been
rewritten for Release 4. It is primarily used for supporting ISA
Plug-and-Play devices, although its services will also be enlisted to
help integrate PCMCIA. The
HOTD
is drivers/config_manager.h
. Stare at
it, then continue reading.
The configuration manager provides five classes of services:
Initializing and uninitializing.
Enumerating and reporting information about devices.
Reporting the current configuration for devices.
Interpreting configuration information.
Reporting possible configurations for devices.
The configuration manager is implemented as a module, a construct introduced in R4 that is accessible only from kernel space. A module exposes itself to drivers via a structure of function pointers in a bid to be a better ioctl than ioctl. Modules are loaded as needed and unloaded when no longer used. To initialize the configuration manager, simply load the module in the usual fashion:
config manager for driver module_info* module
;
if (get_module
(B_CONFIG_MANAGER_FOR_DRIVER_MODULE_NAME
, (module_info **)&module
) < 0) returnB_ERROR
;
Conversely, to uninitialize:
put_module
(B_CONFIG_MANAGER_FOR_DRIVER_MODULE_NAME
);
Out of respect for reference counting, your driver should maintain a one-to-one correspondence between calls to get module and calls to put module.
Once the configuration manager has been loaded, your device driver will feel compelled to scan for devices it knows about. The function
status_t (*get_next_device_info
)( bus_typebus
, uint64 *cookie
, struct device_info *info
, uint32info_size
);
iterates through the devices on the specified bus, placing info size bytes of information about each device in a device info structure. For example, the following loop runs through the ISA devices:
/* cookie = 0 signals start of enumeration */ uint64cookie
= 0; struct device_infoinfo
; while (module
->get_next_device_info
(B_ISA_BUS
, &cookie
, &info
,sizeof
(struct device_info)) ==B_OK
) { ... }
For completeness, the device info declaration:
struct device_info { /* Size in bytes of bus-independent and * bus-dependent data for this device */ uint32size
; /* Offset, relative to the start of the structure, * to the bus-dependent data for the device */ uint32bus_dependent_info_offset
; /* B_ISA_BUS or B_PCI_BUS */ bus_typebus
; /* Device code, a la PCI */ device_typedevtype
; /* "Normally unique and persistent" id for the * device */ uint32id
[4]; /* Device-independent flags */ uint32flags
; /* If config status is B_OK, the device is working * properly. Otherwise, the device is disabled and * should be ignored by device drivers */ status_tconfig_status
; };
The configuration manager will report up to size
bytes of information
about the device. This includes both bus-independent data, as described
by the device_info structure, and bus-dependent data, which appear
beginning at offset bus_dependent_info_offset
in the returned buffer.
Since you can't know how large the device information buffer should be
until after you've read it, the configuration manager provides an
additional function to load the device info for a specific device:
status_t (*get_device_info_for
)( uint64device
, struct device_info *info
, uint32len
);
The revised loop now looks like this (with error checks stripped out):
while (module
->get_next_device_info(B_ISA_BUS
, &cookie
, &info
,sizeof
(device_info)) ==B_OK
) { struct device_info *dinfo
; struct isa_info *iinfo
; /* only worry about configured devices */ if (config_status
!=B_OK
) continue;dinfo
= malloc(info
.size
); module->get_device_info_for
(cookie
, &dinfo
,info
.size
);iinfo
= (struct isa_info *)((char *)dinfo
+info
.bus_dependent_info_offset
); ...free
(dinfo
); }
ISA bus-dependent information is stored as isa_info, defined in
drivers/isapnp.h
,
and PCI-specific data are stored as pci_info, defined
in drivers/PCI.h
.
A driver typically peeks at the bus-dependent
information to determine whether it can control a particular device.
Once a driver has identified a known device, it must fetch the device's configuration. This is done via a pair of functions:
status_t (*get_size_of_current_configuration_for
)( uint64device
); status_t (*get_current_configuration_for
)( uint64device
, struct device_configuration *config
, uint32 len);
These functions are usually called this way:
status_tresult
; struct device_configuration *config
;result
=module
->get_size_of_current_configuration_for
(cookie
); if (result
< 0) returnB_ERROR
;config
= malloc(result
); if (!config
) returnB_ERROR
; if (module
->get_current_configuration_for
(cookie
,config
,result
) <B_OK
) {free
(config
); returnB_ERROR
; } ...free
(config
);
The device configuration is an array of resource descriptors representing the resources (IRQs, DMAs, I/O ports, and memory) assigned to the device. Resource descriptors come in two flavors. IRQs and DMAs are described as masks, while I/O ports and memory are described as ranges. Masks are bitfields, with the nth bit representing the nth IRQ or DMA. Exactly one of the bits will be set in each mask. Ranges are described by two values, the minbase and the len, with the range running from minbase to minbase + len - 1, inclusive.
The following routines help drivers wade through device configurations:
status_t (*count_resource_descriptors_of_type
)( const struct device_configuration *config
, resource_typetype
); status_t (*get_nth_resource_descriptor_of_type
)( const struct device_configuration *config
, uint32n
, resource_typetype
, resource_descriptor *descriptor
, uint32descriptor_size
);
These function precisely as their names and prototypes suggest.
The configuration manager selects configurations for devices based on sets of possible configurations reported by each device. Drivers typically won't ever need to access these configurations; they're provided for the benefit of device management utilities such as the new Devices preference application.
status_t (*get_size_of_possible_configurations_for
)( uint64device
); status_t (*get_possible_configurations_for
)( uint64device
, struct possible_device_configurations *possible
, uint32len
);
possible_device_configurations is an array of device_configuration structures, with each element representing a set of possible configurations. Since the size of a device_configuration is variable, you can't access individual device configurations by directly indexing the array. Instead, you have to walk the structure manually:
#defineNEXT_POSSIBLE
(c
) \ (c
) = (struct device_configuration *) \ ((uchar *)(c
) + \sizeof
(struct device_configuration) + \ (c
)->num_resources
* \sizeof
(resource_descriptor)) struct device_configuration *config
=possible
->possible
+ 0; for (i
=0;i
<possible
->num_possible
;i
++) { ...NEXT_POSSIBLE
(config
); }
Descriptors for possible configurations differ slightly from their current configuration counterparts since they represent a set of possible choices rather than a single selection. Masks may have multiple bits set, with each bit representing a possible IRQ or DMA setting. Ranges are described by a minbase, a maxbase, a basealign, and a len, describing a range starting between minbase and maxbase, in increments of basealign, and having length len.
Most APIs make more sense when you see them in action, so next time we'll develop an application demonstrating the use of the configuration manager.
In this article I'm going to reinvestigate some of the basic building
blocks of the BeOS. We'll look at what I'll call the AppKit model:
BMessage
s, BLooper
s,
BHandler
s, and BMessenger
s.
The BMessage
is fundamentally a data container,
commonly used to hold both instructions and data to be acted upon.
BHandler
s are objects that perform an action when
BMessage
s are delivered to them; they handle the
incoming message. BLooper
s are threaded
BHandler
s that run a message loop, waiting for
incoming BMessage
s and dispatching them to the
appropriate BHandler
s.
BMessenger
s are system wide tokens that represent a
given BLooper
-BHandler
pair,
delivering messages to (and replies from) the specified
BHandler
.
The most visible examples of BLooper
s and
BHandler
s are BApplications,
BWindow
s, and BView
s.
These lie at the heart of the BeOS APIs, and are
common to most BeOS applications. Many developers, however, seem to use
the AppKit model only in their interface areas, where it is pretty much
required. Since the AppKit model has other valid uses, I'm going to offer
some design ideas that may persuade developers to take advantage of its
versatility.
First though, a list of the AppKit model's advantages and disadvantages to keep in mind while reviewing my designs.
Advantages:
Uses a common, familiar system where a great deal of organizational
work is handled by the BeOS. This includes a well-defined communication
system, automatic threading of your app, and built-in object management
through the BLooper
AddHandler()
and RemoveHandler()
functions.
The public class interface is easily extendable by extending the messaging protocol used. The functions to handle the new messages are usually private functions, and can be extended as necessary.
The interface can be exposed to interapplication systems by publishing the messaging protocol. This allows other apps doing complementary work to interact with your application easily.
The system interface is eligible for scripting, since the AppKit model is the basis of the BeOS scripting mechanism.
Disadvantages:
A BLooper
thread's main responsibility of is to run
the message loop, not some other tasks. To have threads work on other tasks
requires using Kernel Kit threads. The BLooper
threading model is therefore not always appropriate to the task at hand.
However, combining the two models can work well (i.e., a
BLooper
with extra, special-purpose threads for
other tasks).
BHandler
s can belong to only one
BLooper
, effectively serializing access to each
handler. This can be problematic in a system where the
BHandler
would (ideally) be accessible by multiple
threads. Some designs can work around the limitation by creating a new
BHandler
subclass for each
BLooper
, but this works only when the
BHandler
s themselves do not encapsulate data that
needs to be instantiated only once.
Adding information to BMessage
s requires copying
that data. This can introduce significant overhead if large amounts of
information need to be transmitted, or if the data needs to be looked at
many times over the course of an operation. Introducing other methods of
data sharing (like putting a reference to a shared memory area or a pointer
to data into the BMessage
) can reduce the size and
complexity of the messages. Note that this might lead to some undesirable
consequences, as the recipient of the BMessage
no
longer has to go through the messaging mechanism to access the data.
Keeping these advantages and disadvantages in mind, here are two design schemes that use the AppKit model: the Handler as Data Object and Handler as Operation.
In this scheme, the BHandler
contains both the data to be acted upon and
the knowledge of how modifications are to be performed. The data is
encapsulated in a self-modifying object. BMessage
s serve as instructions
for what actions to perform. The BLooper
serves as the initial interface
to the various objects, but would usually pass back BMessenger
s for the
appropriate data objects, so the outside processes can deal with them
directly.
Here, the data object can be independently acted upon by multiple
threads/applications while in a guaranteed consistent state. BMessage
s
instruct the server to create, delete, or modify objects. Change
notifications can be sent back to all interested processes. Furthermore,
the transactions can be recorded so that a previous state could be
reinstated by rewinding the transaction stack.
In this scheme, each BHandler
represents an operation that can be
performed on some data. The BMessage
carries the data to modify and
instructions about which operations to carry out. The BLooper
serves as a
common interface to the operations, investigating the instructions and
passing the data to the operations in the correct order, then sending the
modified data back to its origin.
Use BHandler
s to represent add-on filters to manipulate data. Have each
add-on create an entry function that returns a BHandler
that performs the
appropriate filter. Then simply pass the data to each filter as
appropriate. This could be parallelized by calling the entry function for
a new BHandler
for each thread that needs a copy of the filter, at the
expense of more memory.
A BLooper
represents a state machine, with
BHandler
s representing each
state. The looper passes the appropriate instructions off to each state,
which responds to them and asks the BLooper
to change state when
appropriate. In this example, the BLooper
s might contain the data to act
upon, rather than a BMessage
, but the view from the
BHandler
is the same.
You can find some simple example code for these two schemes at:
<ftp://ftp.be.com/pub/samples/application_kit/AppKitModel.zip>
Both schemes perform the same work, transforming strings to uppercase,
lowercase, or mixed case (with every word capitalized.) They implement
the code to modify the strings the same way, using the BString
class
functions: ToUpper()
,
ToLower()
, and CapitalizeEachWord()
.
They also act on the
same two strings. The only difference between the two examples is the
schemes used to organize the code (and correspondingly, the printed
output).
This structure is obviously overkill for simple string modification, but
the schemes become more useful as the complexity of the data and
operations increases. Strings are just an easy way to demonstrate the
various designs. The Notes file in each folder explains how the project
is put together. Also, note that both applications are useful only when
run from the command line, as all feedback is through printf()
calls.
I hope these designs can be helpful in your programs, or at least will start you thinking more about the overall design of your application.
A week ago, when the rumors of AOL's acquisition of Netscape were confirmed, I didn't think it had much immediate impact on our company. Others differed. I received several e-mail messages asking what our position was. Correspondents were of the opinion that every move against Microsoft implicitly favors us, and thought the reference to an "AOL appliance" indicated an opportunity for a BeOS-powered Web appliance. And, last Friday, I found this in the San Jose Mercury News:
<http://www.mercurycenter.com/premium/business/docs/loveqa27.htm>
Selected quotations from the full story:
One leading critic of this acquisition is Jamie Love, an antitrust economist and director of the Ralph Nader-affiliated Consumer Project on Technology in Washington, D.C. His group was among those that successfully lobbied for the government to change WorldCom Inc.'s plans to acquire MCI Communications Corp., the long-distance phone company, earlier this year.
Now, Love wants to block the AOL-Netscape agreement because he believes it eliminates Netscape, a significant Microsoft rival, and gives two companies—AOL and Microsoft—too much power to impose their own company-controlled software on the Internet.
Love discussed his concerns with Mercury News staff in the following edited answers to questions posed by e-mail.
[...]
Q: So what changes might you seek in the AOL-Netscape deal?
A: There are several areas where the AOL-Netscape merger concerns us. Will AOL and Microsoft exert so much power in e-commerce that they can extort revenues from firms that they feature for their customers? Will they undermine non-affiliated technologies for new multimedia services? Will AOL strike a deal with Microsoft to drop support for applications that run on Linux (a version of the Unix operating system that is freely available on the Internet) or other operating systems? Maybe AOL could (be compelled to) agree to support AOL and Netscape on Linux or BeOS (an operating system made by Be, Inc.) and promote greater choices for PC operating systems.
This is healthy publicity, and probably even friendlier in intent than the plug we got from Bill Gates at Microsoft's shareholders meeting almost three weeks ago. But we shouldn't let only others speak on our behalf, however eloquent and motivated.
First, I heard and read suggestions that since the AOL/Netscape deal was against Microsoft then there must be something in it for us. The logic is understandable: Microsoft abuses its dominant position, so let's do anything we can to diminish their power. This is flawed logic, however. A benevolent regime doesn't automatically follow a toppled tyrant, as history and contemporary events demonstrate painfully enough.
In our view, being against Microsoft isn't good or bad in itself. What is good is more choice—real choice—not, for example, the kind we used to have with only two cellular service providers in town, GTE and Cellular One. Economists established long ago that oligopolies aren't really an improvement over monopolies. With this in mind, let's hope the AOL/Netscape deal really does create more choice, not the kind of diversity that results only in more people like us, not more people like them.
(For another very good piece on the topic, see Denise Caruso's November 30th column in the New York Times:
<http://www.nytimes.com/library/tech/98/11/biztech/articles/30digi.html>).
Personally, I imagine Microsoft thinks there must be a god and she likes Bill. Try selling this script in Hollywood: right in the middle of the "trial of the century," two of the cyber-tyrant's alleged victims merge to form an Internet powerhouse that subverts, if not entirely kills off, the DOJ's argument. The studio would banish you to North Korea on a mission to teach market economics.
Just ten days ago, Microsoft's PR machine was straining against the bad impressions generated by Bill Gates' video deposition. Now they're working full time on the new message: We told you so, Microsoft is a company constantly under competitive threat.
As for Web appliances, yes, the concept is gaining momentum and the BeOS could make a contribution at the intersection of two requirements, rich multimedia user experience, the NC with charm, and a small core enabling fast, inexpensive devices. That would create more choice. Unless Microsoft, never asleep at the wheel, embraces and extends it—as always. I hear they already have a name for it. PortalPC. Has a nice ring to it.