2008-04-07

Context Menu in Progress

The context menu is going quite well. You can see it working in the demo - hold down one of the shoulder buttons and tap the default bottom screen and a test menu will appear. Alternatively, bring down the PacMan screen and shoulder-click the PacMan window - you’ll get a context menu with two (two!) functioning options.

The context menu is implemented as a gadget within the Woopsi class. When the Woopsi singleton class is instantiated the menu gets created, added to the child list, and hidden. Each time it is shown, it gets moved from the hidden array back into the main array (which ensures it’s always at the top of the gadget stack), and every time it is closed it gets moved back to the hidden list.

Working with the context menu is very, very easy. Gadgets now have a list of name/value pairs that get automatically added to the context menu when a user shoulder-clicks on the gadget. If there are any items in the list, Woopsi automatically builds and displays the context menu. Clicking an item causes the gadget that opened the menu to raise a “EVENT_CONTEXT_MENU_SELECTION” event. Selecting an item, or clicking anywhere but within the menu, automatically closes it.

Building up a context menu works lilke this:


AmigaWindow* w = new AmigaWindow(blah);
w->addContextMenuItem("Item 1", 0);
w->addContextMenuItem("Item 2", 1);
w->addContextMenuItem("Item 3", 2);

This creates a menu definition for the AmigaWindow object with three items. The first parameter is the name of the menu item, the second is its value. When the user shoulder-clicks the window, the context menu will automatically open with the three items displayed. These values could, of course, be added in the constructor if you are subclassing (instead of added outside the class).

Interpreting menu selections can be handled in two ways. The easiest way is to give your gadget an event handler, and have that handle the event in the usual way:


void handleEvent(EventArgs e) {
    switch (e.type) {
        case EVENT_CONTEXT_MENU_SELECTION:
            switch (woopsiApplication->getContextMenuValue()) {
                case 0:
                    // Do something for the first option
                    break;
                case 1:
                    // Do something for the second option
                    break;
            }
            break;
    }
}

The function “woopsiApplication->getContextMenuValue()” will return the value of the last selected menu item.

The alternative way to handle menu selection (in case you are subclassing and don’t want to muck about with event handlers) is to override the “Gadget::handleContextMenuSelection()” function. The selected menu value is passed in as an argument, so you can switch() the value directly.

Doing all of this involved some changes. First of all, there’s the new “EVENT_CONTEXT_MENU_SELECTION” event. There’s also a new “EVENT_SHIFT_CLICK” event, which is raised when a gadget is shoulder-clicked (I’m avoiding “right click” as a term, and “shift click” suggesting a modifier key seemed like a non-hardware specific alternative). There’s a new “Gadget::shiftClick()” function that works in much the same way as “Gadget::click()“, and there’s a new “Gadget::raiseShiftClickEvent()” function. Woopsi’s “handleClick()” function now checks to see if it should close the context menu after it has passed the click event through the hierarchy.

There’s also a new value in the gadget’s _flags struct - “shiftClickChildren”. If this is set to false, shift clicks will not get sent to the gadget’s children and instead are processed by the gadget itself. This is useful in two circumstances. If you have a window, for example, and want the same context menu to show regardless of whether the user clicks the window or a button within a window, you should call “setShiftClickChildren(false)”. This will prevent the window from trying to get its children to handle the click instead of itself. The window’s context menu will therefore be shown regardless of whether the user clicks the window or one of the window’s children.

The second instance where it is useful is for compound gadgets, or gadgets made up of several other gadgets (ie. the ScrollingTextBox, which is composed of a MultiLineTextBox and a ScrollbarVertical, which itself consists of three other gadgets, one of which also consists of multiple gadgets). In this instance, only the main gadget should be capable of opening a context menu.

There are a few bugs remaining. The menu doesn’t seem to grow horizontally to accommodate all of its items correctly, nor does it try to keep within the bounds of the screen. It should be possible to suppress its focus-grabbing (if it does actually grab the focus; I’m not certain) by overriding its shiftClick() and click() method. Lastly, I managed to make it crash the real DS so I need to check for pointer problems.

Not a bad start, though.

Oh, a couple of bug fixes elsewhere in the code. The “Textbox::resize()” method remembers the value of the visible flag correctly (wouldn’t believe how much trouble that caused me), the gadget class’ destructor calls “destroy()” on its children instead of deleting them (dunno how that was still functioning) and “Gadget::show()” correctly invalidates its parent’s visible rect cache.