Issue 3-48, December 2, 1998

Be Engineering Insights: R4 Drag-and-Drop

By Jon Watte

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!


Be Engineering Insights: The Configuration Manager, Part I

By Victor Tsou

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:

  1. Initializing and uninitializing.

  2. Enumerating and reporting information about devices.

  3. Reporting the current configuration for devices.

  4. Interpreting configuration information.

  5. Reporting possible configurations for devices.

1. Initializing and uninitializing.

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)
  return B_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.

2. Enumerating and reporting information about devices.

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_type bus,
    uint64 *cookie,
    struct device_info *info,
    uint32 info_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 */
uint64 cookie = 0;
struct device_info info;

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 */
  uint32    size;

  /* Offset, relative to the start of the structure,
   * to the bus-dependent data for the device */
  uint32    bus_dependent_info_offset;

  /* B_ISA_BUS or B_PCI_BUS */
  bus_type  bus;

  /* Device code, a la PCI */
  device_type  devtype;

  /* "Normally unique and persistent" id for the
   * device */
  uint32    id[4];

  /* Device-independent flags */
  uint32    flags;

  /* If config status is B_OK, the device is working
   * properly. Otherwise, the device is disabled and
   * should be ignored by device drivers */
  status_t    config_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)(
    uint64 device,
    struct device_info *info,
    uint32 len);

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.

3. Reporting the current configuration for devices.

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)(
    uint64 device);

status_t (*get_current_configuration_for)(
    uint64 device,
    struct device_configuration *config,
    uint32 len);

These functions are usually called this way:

status_t result;
struct device_configuration *config;

result = module->get_size_of_current_configuration_for(cookie);
if (result < 0) return B_ERROR;
config = malloc(result);
if (!config) return B_ERROR;
if (module->get_current_configuration_for(
    cookie, config, result) < B_OK) {
  free(config);
  return B_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.

4. Interpreting configuration information.

The following routines help drivers wade through device configurations:

status_t (*count_resource_descriptors_of_type)(
    const struct device_configuration *config,
    resource_type type);

status_t (*get_nth_resource_descriptor_of_type)(
    const struct device_configuration *config,
    uint32 n,
    resource_type type,
    resource_descriptor *descriptor,
    uint32 descriptor_size);

These function precisely as their names and prototypes suggest.

5. Reporting possible configurations for devices.

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)(
    uint64 device);

status_t (*get_possible_configurations_for)(
    uint64 device,
    struct possible_device_configurations *possible,
    uint32 len);

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:

#define NEXT_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.


Developers' Workshop: Back to Basics

By Stephen Beaulieu

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: BMessages, BLoopers, BHandlers, and BMessengers.

The BMessage is fundamentally a data container, commonly used to hold both instructions and data to be acted upon. BHandlers are objects that perform an action when BMessages are delivered to them; they handle the incoming message. BLoopers are threaded BHandlers that run a message loop, waiting for incoming BMessages and dispatching them to the appropriate BHandlers. BMessengers 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 BLoopers and BHandlers are BApplications, BWindows, and BViews. 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:

Disadvantages:

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.

Handler as Data Object

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. BMessages serve as instructions for what actions to perform. The BLooper serves as the initial interface to the various objects, but would usually pass back BMessengers for the appropriate data objects, so the outside processes can deal with them directly.

Example: Transaction Server

Here, the data object can be independently acted upon by multiple threads/applications while in a guaranteed consistent state. BMessages 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.

Handler as Operation

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.

Example: Data Filters

Use BHandlers 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.

Example: State Machine

A BLooper represents a state machine, with BHandlers 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 BLoopers 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.


Our Take on AOL's Acquisition of Netscape

By Jean-Louis Gassée

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.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.