Categories
Articles

Raising the (tool)bar

When AmigaOS 3.5 was released back in 1999, one of the things I welcomed as an enhancement was the new ReAction GUI toolkit. Although in actuality it was little more than a rebadged third-party product originally called ClassAct, its inclusion as an official part of the operating system made all the difference. Commodore’s object-oriented GUI framework (BOOPSI) had been in place for years but before ReAction came, the framework lacked a comprehensive toolkit to provide elements needed for modern GUI programming.

The accompanying Developer CD V2.1, released in the wake of the updated OS, contained enough documentation and examples for the programmer to start using the new toolkit. Combined with the fact that BOOPSI in general is quite straightforward to use, ReAction was adopted relatively quickly on post-OS3.5 systems including AmigaOS 4.x, where it represents the default GUI toolkit.

However, there are certain things in ReAction that reveal its 1990s mindset and that, with the benefit of hindsight, cannot exactly be called good design choices. For example, some of the gadget classes are a bit quirky to set up and use because they require the programmer to first allocate and populate an Exec list, and then use dedicated functions provided by the class to manipulate the gadget object. This is neither clever (low-level stuff such as lists is imposed on a high-level API) nor needed (the existing set of Intuition’s BOOPSI-related functions is good enough to manipulate any type of object, regardless of complexity). So when we started developing what later became known as the Enhancer Core Classes, we decided to stand clear of the old practice.

The recently released ToolBar Gadget (an enhanced alternative to the system’s SpeedBar Gadget) shows that even a complex BOOPSI class can be implemented with maximum ease of use and programmer comfort in mind. The entire gadget can be set up in a single NewObject() call, i.e. at object-creation time, or freely (re)configured later via Intuition’s setter functions: SetAttrs(), SetGadgetAttrs() or RefreshSetGadgetAttrs(). No need to mess with Exec lists, nodes, or special-purpose functions.

As a warm-up exercise, let’s create a very simple toolbar which only uses text instead of graphic buttons. The gadget set-up code pretty much boils down to the following:

Object *toolbar;

CONST_STRPTR button_labels[] = {"New", "Open", "Save", NULL};

toolbar = IIntuition->NewObject(ToolBarClass, NULL,
          GA_RelVerify, TRUE,
          TOOLBAR_LabelArray, button_labels,
          TAG_END);

Configuring graphic toolbars requires some additional work of course, but before we show any more example code, let’s first review some fundamental concepts and ideas. In particular, we’ll focus on the things that make the ToolBar Gadget different from the SpeedBar.

Every element that makes up a ToolBar object is called a member. There are currently five types of member:

  • button: a regular press-me-for-action button;
  • toggle: a Boolean (on/off) button;
  • separator: a dividing element;
  • spacer: a spacing element;
  • textbox: a read-only element to display text (V53.4)

The member type is set at object creation time, and cannot be changed. The default type is button.

In the SpeedBar Gadget, an element is referred to by a pointer to an Exec list node. (In other words: the class exposes the actual implementation to the programmer, which I think is wrong.) When you want to modify an element, you use a dedicated function, SetSpeedButtonNodeAttrs(), passing the node pointer as an argument to identify the element.

The ToolBar Gadget, on the other hand, treats its elements (members) strictly as “black boxes”: in accordance with BOOPSI principles, you know nothing about their internals or implementation. All you need is a custom ID to refer to the member. IDs are numbers and, like member types, can only be set at object creation time. When you want to modify a member, you use Intuition’s standard BOOPSI setter functions in which you provide the member ID followed by the attribute(s) you want to set for the given member. Several members – all of them if you want – can be modified at once via a single call, which is something you cannot do with SetSpeedButtonNodeAttrs(). To give you an idea, let’s say we have three toolbar buttons we want to disable in one go:

IIntuition->SetGadgetAttrs((struct Gadget *) toolbar, window, NULL,
            TOOLBAR_SetMember, TBID_NEW,
               TBMEMBER_Disabled, TRUE,
            TOOLBAR_SetMember, TBID_OPEN,
               TBMEMBER_Disabled, TRUE,
            TOOLBAR_SetMember, TBID_SAVE,
               TBMEMBER_Disabled, TRUE,
            TAG_END);

Done! (There’s an even easier way to do this if you use member groups, but let’s not get into too much detail. The class documentation has it all if you are curious.)

Another major area of difference is the way button images are handled. With the SpeedBar, you need to create the image outside of the class (typically, by using the BitMap Image class) and then associate the image pointer with the given speedbar node. The ToolBar offers more comfort in that it can create the image – and rescale it, if needed – for you automatically if you provide the file name of the image source. But more importantly: you do not associate image pointers with ToolBar members directly (like you do with the SpeedBar). Instead, you create an image set (or multiple image sets) in which each image bears a unique ID. So a member is configured for particular imagery via an image ID rather than a direct image pointer. In program code, you’ll typically define the image set(s) before you configure the members, for easier referencing. (But this is just a suggested convention. The class is flexible enough to allow configuring the imagery at a later point should you prefer that.)

toolbar = IIntuition->NewObject(ToolBarClass, NULL,
          GA_ID, GID_TOOLBAR,
          GA_RelVerify, TRUE,

          /* configure the image set */
          TOOLBAR_AddImage, TBID_NEW,
             TBIMAGE_Image, myImagePointer1,
          TOOLBAR_AddImage, TBID_OPEN,
             TBIMAGE_Image, myImagePointer2,
          TOOLBAR_AddImage, TBID_SAVE,
             TBIMAGE_Image, myImagePointer3,

          /* configure the buttons */
          TOOLBAR_AddMember, TBID_NEW,
             TBMEMBER_ImageID, TBID_NEW,
          TOOLBAR_AddMember, TBID_OPEN,
             TBMEMBER_ImageID, TBID_OPEN,
          TOOLBAR_AddMember, TBID_SAVE,
             TBMEMBER_ImageID, TBID_SAVE,
          TAG_END);

A great advantage of this implementation is that multiple image sets can easily be defined for a single toolbar, in order to allow a variant look based on user preference. With the current display technology, AmigaOS4 applications may soon need to support different-sized toolbar image sets to cater for both 4K and lower screen resolutions. The ToolBar Gadget facilitates this task because flipping between the configured image sets is a matter of changing one attribute at the toolbar level. When switching to a new image set the gadget will replace all member images automatically, retrieving them from the set based on the image IDs.

I think this might be good enough for starters. I suggest you first have a look at the source code of the examples that come with the class as part of the Enhancer SDK installation, and then go through the extensive documentation. I’m sure you will find the ToolBar Gadget straightforward to use, and I’m looking forward to seeing it adopted in future AmigaOS4 software!

Leave a Reply

Your email address will not be published. Required fields are marked *