Using AxTk


Overview

Let's look at the main elements and classes in AxTk, and how they interact. First we'll cover adaptation classes that you can use with your existing application; next we'll examine the new menu system that you can consider using to make homogenous, easy-to-navigate interfaces.

User interface adaptation classes

It can take just a few lines to adapt a dialog or frame to self-voicing.

#include "ax/ax_ui_adaptation.h"

...


class MyDialog: public wxDialog
{
    ...
    AxSelfVoicing m_adapter;
};

MyDialog::MyDialog(...)
{
    ...
    m_adapter.Adapt(this);
}

In addition, you need to create an AxViewManager class as described in the next section to look after housekeeping tasks such as managing speech engines.

AxSelfVoicing is doing a lot behind the scenes - when Adapt is called, the control hierarchy is examined and special event handlers derived from AxAdapter inserted into each control's event chain. Each such event handler gives extra information about the control, for example the control's label, a detailed description of the control's state, and so on. The event handler can respond to keyboard input and can process shortcuts. All AxAdapter objects are removed in the AxSelfVoicing destructor, so it's important to add an AxAdapter object to the window class, rather than create it on the stack.

How are these AxAdapter objects assigned to controls? This is done by instances of AxVoiceAdaptationHandler, which are added to static lists in AxSelfVoicing on application startup. Each AxVoiceAdaptationHandler object can recognize one or more controls and create a corresponding AxAdapter for the ones it knows about. You can add further handlers if you wish to extend the range of recognised controls.

You can also add an adapter explicitly on a control-by-control basis. You might want to do this if you want to assign specific information to the adapter. Once you've added the adapter, AxSelfVoicing::Adapt will ignore it and will not add a second adapter. Here's an example of adding an adapter to a button to tell the system to read an alternate label:

    wxButton* button = new wxButton(dialog, ID_PLAY, _(">"));
    AxSelfVoicing::AddAdapter(button, new AxButtonAdapter(_("Play")));

Convenience dialogs

AxTk provides a number of dialogs to replace the relatively inaccessible dialogs in standard wxWidgets, or to supply user interface functionality that cannot be supported with menus alone.

AxMessageBox and AxMessageDialog can be used in the same way as wxMessageBox and wxMessageDialog. Instead of showing the message in a static text control, the text is displayed in a multiline text control to make it easier to navigate to and around the message. Also, some extra buttons are supported. You can pass wxYES_TO_ALL and wxNO_TO_ALL, which will be returned if the user presses one of these buttons (the return values will be wxID_YESTOALL and wxID_NOTOTALL if using wxMessageDialog instead of wxMessagBox).

AxTextInputDialog can be used in place of wxTextEntryDialog, and can be used to enter multiline text if the flag axTEXTDIALOG_MULTILINE is passed. It makes use of the AxTextCtrl class which supports the following keyboard shortcuts:

AxIdentifierReadCurrentWord, AxIdentifierReadNextWord, AxIdentifierReadPreviousWord, AxIdentifierReadCurrentSentence, AxIdentifierReadNextSentence, AxIdentifierReadPreviousSentence, AxIdentifierReadCurrentParagraph, AxIdentifierReadNextParagraph, AxIdentifierReadPreviousParagraph, AxIdentifierReadFromHere, AxIdentifierReadAll, AxIdentifierDescribeContext, AxIdentifierStopSpeech, AxIdentifierPauseResumeSpeech.

AxShortcutInputDialog is similar to AxTextInputDialog but is specialised for entering the elements of a shortcut one key at a time. It's used by AxShortcutsMenuItem for editing application shortcuts.

The AxLogGui class is not a dialog, but implements log display using an accessible dialog. You can use it in your application initialisation as follows:

    #include "ax/ax_log.h"
    ...
    
    delete wxLog::SetActiveTarget(new AxLogGui);

The menu system

Introduction to the menu system

With AxTk's alternate menu-based interface, the user primarily interacts with the application via a control that acts as a menu. This can be implemented using any suitable control, but the default implementation in AxTk uses a wxListCtrl. A typical user interface will consist of the menu control, a menu title control, a description control, a view area for showing specific items, and perhaps some buttons for commonly used commands such as going up and going home. AxTk provides a default user interface with these elements in the form of AxContainerCtrl, but you can provide your own if you wish.

A menu is represented by the AxMenu class, which contains one or more AxMenuItem objects. An AxMenuItem object can have a submenu, and various subclasses define further behaviour such as file browsing and toggling between two states.

The means by which the application processes events from the menu is a bit different from the way wxWidgets normally works. Each AxMenuItem can have an AxActivator object, which has methods that are called when the user selects or activates the menu item. However you can use the AxEventActivator class and pass an event handler object and command identifier in order to send a wxCommandEvent to an object, such as the main frame or application object.

When AxActivator::Activate is called by the system, an AxMenu object can be returned. If so, the system takes control of the menu and the menu is shown. There is only one main menu, and the system takes responsibility for managing the menu objects as soon as they are handed over. If the application marks a menu item as dynamic, its submenu will be deleted before showing a new one created by the same activator object, allowing for the fact that data may change over time, and also allowing submenus to be cleaned up when not in use.

Menu interaction and presentation is routed through an AxMenuController object, which knows what kind of control is being used to implement the menu. The class AxStandardMenuController is provided using wxListCtrl as its menu control. The controller object is passed to activation and other functions and the application can use functions in the controller object, for example to refresh a menu item.

An AxTk application needs to store an AxViewManager object, usually a member of the application class. AxViewManager stores the menu controller object and manages AxView objects which are used to display information in the view area (typically on activation of a menu item).

To show your top-level menu, call AxViewManager::ShowInitialMenu.

Menu items and activators

AxTk provides a number of classes derived from AxMenuItem that provide useful pre-defined functionality. These include:

To achieve this functionality, some of the above use a modal menu, derived from AxModalMenu. This class shows a temporary menu using AxModalMenu::ShowModal which interrupts program flow (and 'captures' the user in the submenu) until an activator in the submenu calls AxModalMenu::EndModal. When the modal menu is destroyed, the previous menu is restored.

You can use the modal menu functionality from an activator, without an associated AxMenuItem. For example you could prompt for a string with AxModalChoiceMenu. The AxAlertModalMenu class doesn't have an associated menu item since it's used for a temporary alert state.

Although menu items such as AxTextMenuItem and AxRangeMenuItem contain their own state storage, you may wish to use a corresponding validator object such as AxTextValidator or AxRangeValidator to link the menu item with a C++ variable. This is similar to wxValidator in regular wxWidgets programming.

AxAutoUpdateActivator is a useful base class for activators that need to update their menu items in idle time or just before the items are displayed. Derived classes need to implement the CreateString function to provide the current label or description text given a label or description specification that is stored in the AxAutoUpdateActivator class. Typically, CreateString will be implemented by replacing a keyword in the specification with the current value. AxAutoUpdateActivator takes care of implementing the BeforeDisplay and UpdateUI function that the system calls to allow label updating to occur.

Event handling

Each AxMenuItem has a string identifier. Using strings instead of integers reduces the chances of accidental identifier clashes, and the headache of allocating identifiers by hand.

Menu items send command events to the menu item when their values are changed. At the moment, AxTk reuses event classes and types from wxWidgets, so for example AxRangeMenuItem will generate EVT_SPINCTRL when the user changes the value. Command events are processed first by the menu item, then by its validator, and if not processed by either, the event is passed up the menu hierarchy. Since menu items have string identifiers, use the macro AxId to turn string identifiers into integers for passing to EVT_... macros. You can also use the function AxGetId to return an integer identifier.

Here's an example of an event table defined for a menu item:

BEGIN_EVENT_TABLE( AxSpeechSettingsMenuItem, AxMenuItem )
    EVT_CHOICE(AXID("SPEECHENGINE_MAIN"), AxSpeechSettingsMenuItem::OnSelectEngine)
    EVT_SPINCTRL(AXID("VOLUME_MAIN"), AxSpeechSettingsMenuItem::OnSelectVolume)
    EVT_CHECKBOX(AXID("ENABLE_MULTIPLEVOICES"), AxSpeechSettingsMenuItem::OnSelectMultipleVoices)
END_EVENT_TABLE()

You can see examples of event handling in ax_menu_speech_settings.cpp.

Managing online help

Current AxTk doesn't support full-blown online help, though it is expected that this will follow. However it can support context-sensitive help, normally activated using the F1 key.

Add this to your application initialisation code:

    #include "ax/ax_help_provider.h"
    ...

    wxHelpProvider::Set(new AxHelpProvider(& GetViewManager().GetSpeech()));

AxHelpProvider implements a popup HTML window with speech output for the help strings that you set for controls with wxWindow::SetHelpText.

For menu-based interfaces, menu items and menus have descriptions which are displayed underneath the menu and are spoken when the item is described (depending on current verbosity level and the operation that causes the item to be described).

Documents and views

If you're using the AxTk menu system, you are likely to use AxView and possibly AxDocument to manage information displayed in the view pane, to the right of the menu. Views can be shown by menu activators or other code. To create a view class, derive from AxView and override functions such as AxView::Initialize (create necessary windows) and AxView::Activate (to show the UI with the appropriate document). In fact you don't need to associate a document with a view; for example, with AxSummaryView you can just call AxSummaryView::SetDescription to display some text.

Here's some code that uses AxSummaryView to show initial text for the application:

// Show the home view
bool AxViewManager::ShowHomeView()
{
    AxSummaryView* view = wxDynamicCast(FindOrCreateView(CLASSINFO(AxSummaryView)), AxSummaryView);
    if (view)
    {
        ActivateView(view, NULL);
        view->SetDescription(GetHomeDescription());
        
        // Set the focus back to the menu control in case it changed
        // when creating a view
        GetMenuCtrl()->SetFocus();
        return true;
    }
    return true;
}

A view generally creates its window as a child of the pager control (similar to a notebook, but without any visible controller). Here's how AxSummaryView initializes itself, by adding a named window to the pager control:

bool AxSummaryView::Initialize(AxViewManager* viewManager)
{
    if (!GetWindow())
    {
        AxSummaryViewPanel* panel = wxDynamicCast(viewManager->GetViewPagerCtrl()->FindPageWindow(wxT("Summary")), AxSummaryViewPanel);
        if (!panel)
        {
            panel = new AxSummaryViewPanel(viewManager->GetViewPagerCtrl(), wxID_ANY);
            viewManager->GetViewPagerCtrl()->AddPage(panel, wxT("Summary"));
        }
        SetWindow(panel);
    }
    return true;
}

Although showing one view at a time may seem restrictive, it provides a simple model for the visually impaired user to interact with. However if you wish to provide a completely different interface, that's fine, though you'll still need an AxViewManager instance in order to manage menu interaction and/or adaption of a conventional wxWidgets user interface.

Currently, AxDocument isn't used by the basic AxTk framework and its sample. However, AxDocument comes into its own in the resource subsystem.

Appearance customisation

Currently, the user can perform very simple customisation of the application text size and colours. As the samples demonstrate, the Visual Settings menu has options for text size, text colour, window colour, and panel colour. Each settings has a default option which allows the platform defaults to be used.

The application can also control these settings programmatically via the AxVisualSettings class which is accessed using AxViewManager::GetVisualSettings.

These settings can be loaded from and saved to wxConfig objects via AxVisualSettings::Load and AxVisualSettings::Save, as shown in the samples to make the user choices persist between sessions.

In future, visual schemes may be added to make it easier to choose multiple settings at once, using names such as Large Text or High Contrast.

How appearance update is implemented

The appearance is changed when AxViewManager::UpdateAppearance is called with an optional window to start from and update hint. After calling AxMenuController::UpdateAppearance (implemented by the specific menu controller class), the function sends a wxSysColourChangedEvent to the specified window (or top-level window). Making use of this existing event means that code that is already set up to handle colour changes may already work correctly. If not, existing code can be extended easily to handle this event.

Also, each AxAdapter object associated with a control responds to this event and calls the virtual functions AxAdapter::ApplyWindowColours and AxAdapter::ApplyFontSize, which can be overridden by specific adapter classes. AxAdapter::PostUpdateAppearance is called after an adapter's children have been sent the wxSysColourChangedEvent event, to allow for a container window to adjust its layout as a result of changing text size. This is performed automatically by AxPanel, but if you have custom layout code not using wxWindow::Layout, you may wish to override this function.

See ax_ui_adaptation.h, ax_ui_adaptation.cpp, ax_ui_adaptation_standard.h and ax_ui_adaptation_standard.cpp for examples.

Managing shortcuts

It's important for a person with sight difficulties to be able to navigate around the user interface using the keyboard. The wxShortcutManager class should be used to manage shortcuts, allowing easy customisation by the user. Controls that have been adapted for speech will become sensitive to shortcuts, due to AxAdapter's own shortcut detection code (independent from the wxWidgets accelerator table mechanism).

An AxTk application will add a wxShortcutManager member to the application class, and initialise the shortcuts with code similar to this:

#include "shortcutcust/shortcutcust.h"
...

void AxSampleApp::InitShortcuts()
{
    // Note that we use wxTRANSLATE for the names, since this must be unique whatever the
    // current language setting is. wxTRANSLATE indicates to tools such as poEdit that the
    // string should be translated, but this macro doesn't actually do the translation.
    // Instead, wxGetTranslation is used later when displaying the name in the menu.
    GetShortcutManager().Add(
        wxShortcutItem(AxGetId(AxIdentifierGoUp),
                       wxTRANSLATE("Go up"), _("General Shortcuts"), _("Goes up to the previous menu"), wxACCEL_CTRL, (int) 'U'));
    GetShortcutManager().Add
        (wxShortcutItem(AxGetId(AxIdentifierGoHome),
                        wxTRANSLATE("Go home"), _("General Shortcuts"), _("Goes to the home menu"), wxACCEL_CTRL, (int) 'H'));
    GetShortcutManager().Add(
        wxShortcutItem(AxGetId(AxIdentifierStopSpeech),
                       wxTRANSLATE("Stop speaking"), _("General Shortcuts"), _("Stops speaking"), wxACCEL_CTRL, (int) 'G'));
    GetShortcutManager().Add(
        wxShortcutItem(AxGetId(AxIdentifierPauseResumeSpeech),
                       wxTRANSLATE("Pause or resume speech"), _("General Shortcuts"), _("Pauses or resumes speech"), wxACCEL_CTRL, (int) 'K'));
    
    ...
}

Shortcuts can be saved to and loaded from a wxConfig object using wxShortcutManager::Save and wxShortcutManager::Load.

The shortcut manager should be associated with the view manager with code similar to:

    wxGetApp().GetViewManager().SetShortcutManager(& wxGetApp().GetShortcutManager());

Add a shortcut editor to your AxTk menu with this code:

    menu->AddItem(new AxShortcutsMenuItem(controller, & wxGetApp().GetViewManager().GetSpeech(),
        & wxGetApp().GetShortcutManager(), this /* the window to set the shortcuts in */,
        GetMenuBar()));

If your application has a frame menu bar, and you pass it to AxShortcutsMenuItem, its labels will be updated appropriately when the user assigns a different shortcut.

You can use the identifiers defined in ax_menu.h, and you can also use your own identifiers. Many of the standard identifiers are handled by event handlers in AxAdapter, but you can add your own event handlers to either your main frame class or (if you need them to be active even when modal dialogs are shown) to your application class.

Using text to speech

wxTextToSpeech

AxTk uses the wxTextToSpeech class, which is implemented separately from core AxTk but is currently included with the AxTk distribution. wxTextToSpeech does not implement a specific speech engine, but instead supports an extensible handler system that makes use of existing speech engines. Currently, wxTextToSpeech has handlers for Microsoft SAPI 5.1, Apple Speech Synthesis Manager, eSpeak, and Cepstral. The handlers for the latter two engines use an external process to drive a command line version of the engine.

AxTk and speech

AxTk abstracts its speech capabilities a step further with the AxSpeech class that can handle up to two instances of wxTextToSpeech. AxSpeech normally processes speech requests in idle time, which allows for utterances to be overridden by more pertinent ones if necessary, before speech synthesis of the old speech event has started.

To speak some text, you can create an AxSpeechEvent object and pass it to AxSpeech, or you can use the convenience function AxSpeech::Say to directly pass the text. The AxSpeechEvent objects are processed (in idle time) by instances of AxSpeechEventHandler which make up an event handler chain. By default, AxSpeech sets the handler to AxStandardSpeechEventHandler. Your application can call AxSpeech::Push to insert a different handler at the front of the chain, and process speech events in a custom way by overriding AxSpeechEventHandler::ProcessSpeechEvent.

An AxSpeechEvent has a string event type, which takes values such as AxSpeechEventActivateItem, AxSpeechEventRefreshMenu, and AxSpeechEventText. Types such as AxSpeechEventActivateItem require the speech text to be constructed from current values such as the current menu item, whereas AxSpeechEventText indicates a simple text string.

AxSpeechEvent contains a flag field, which consists of flags as follows:

You can use the simple AxSpeech::Say function to speak text:

    GetViewManager().GetSpeech().Say(_("Welcome to the Eh Ex Tee Kay sample!"),
        AxSpeechFlagsInviolate); // This shouldn't be interrupted by other speech events.

or you can create and pass an event object:

    AxSpeechEvent event;

    // This is not the verbosity of this event, it's the verbosity with which
    // to describe the event.
    event.SetVerbosityPolicy(describeAtVerbosity);
    event.SetSpeechEventType(AxSpeechEventDescribeItem);
    event.SetFlags(AxSpeechFlagsPurge);
    event.SetMenu(item->GetParent());
    event.SetMenuItem(item);

    GetViewManager().GetSpeech().EmitSpeech(event);

Controlling verbosity

AxSpeech stores speech policy - that is, high-level speech settings that can be changed by the user - using a class called AxSpeechPolicy. As well as containing the switch for switching speech on and off, it stores the current verbosity, which is a number between 1 and 5 and determines how much speech is emitted. 1 is least verbose, and 5 is most verbose; the default is 3.

AxSpeechEvent also stores a verbosity policy that tells the speech event handlers the threshold for this particular speech event. So if you specify a verbosity of 2 in the speech event, then if the global verbosity is 2 or more, it will be spoken. If you specify a verbosity of 1 in the speech event, it will always be spoken.

Channels: using multiple voices simultaneously

AxSpeech supports the concept of 'channel'. There are two channels, represented by integers 0 and 1 or AxSpeechMainChannel and AxSpeechContentChannel. Each channel has a corresponding wxTextToSpeech object and for those engines that support it, they can be used simultaneously. This is useful when outputting content without interrupting it with user interface navigation speech. The user can specify parameters such as voice and speed independently for each channel, and it's likely that you will want to use a different voice for each channel.

Specify the content channel in a AxSpeechEvent by passing the AxSpeechFlagsContentChannel flag. Otherwise, the main channel will be used.

Note that not all engines are capable of outputting speech to more than one channel at a time. Some engines will simply crash, as they are not designed to be re-entrant. wxTextToSpeech has a function wxTextToSpeech::VoicesAreCompatible and AxTk will use it to try to determine whether to switch off two channel operation. However it's possible that you may be using a speech engine that isn't covered; so for SAPI, you can call wxTTSSapiHandler::SetSimultaneousVoiceExclusions to set the strings that will be matched against SAPI voices to determine whether an engine can be used multiple times simultaneously. See also todo.txt for notes about problems with wxTTSProcessHandler::Stop which contribute to problems with simultaneous engine usage.

Using AxResourceLibrary

This is an optional layer on top of AxTk, that maintains a library of resources which can be audio files, ebooks, text documents, utilities, web services, or whatever might be a useful feature of the application. The motivation is to present users with a uniform and easy way to access their data and important functionality. Providing this in AxTk means that others can take an already useful system (in particular with support for popular audio and ebook formats) and build upon it, providing custom solutions.

The intention is to separate out the major functionality from the way it's used in AxTk, where possible. So for example the guts of Epub manipulation will be done in AxTk-neutral classes, with AxTk-specific classes to integrate the functionality. This allows programmers to integrate the functionality in different ways, as well as making maintenance easier.

The sample in samples/ax_resource/resource_sample can scan folders containing audio files and Epub files, and can then play albums and read Epub books (converting the XHTML content to plain text).


Generated on Wed May 6 19:22:06 2009 for AxTk by  doxygen 1.5.8