Tab Order

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search

Written for CEGUI 0.5


Works with versions 0.5.x (obsolete)

Written for CEGUI 0.6


Works with versions 0.6.x (obsolete)

Introduction

This code provides Tab and Shift+Tab navigation through a list of widgets.

Create an instance of TabNavigation for each group of tab order, such as one per dialog. Specify the parent via a call to setParent(). This will help catch Tab and Shift+Tab keys, which will then properly cycle through the widgets specified via addWidget(). Note that addWidget() accepts window names as well as the handle of TabControl; this will automatically add the TabButton of that TabControl.

Please discuss this snippet within the Tab Order thread.

Files

TabNavigation.h

#ifndef _TabNavigation_h_
#define _TabNavigation_h_
 
#include <vector>
#include "CEGUI.h"
 
class TabNavigation
{
public:
	/* Specifies the parent or container.
		Used to trap Tab and Shift+Tab keys and relay them to the list of tab navigation. */
	void setParent(const CEGUI::String& window);
 
	/* Adds a TabControl widget.
		Its tab buttons will be added to the list of tab navigation. */
	void addWidget(const CEGUI::TabControl* tabControl);
 
	/* Adds a widget to the list of tab navigation.
		The order in which they are added corresponds to the tab order. */
	void addWidget(const CEGUI::String& window);
 
private:
	/* Ensures that the last known focused widget regains input. */
	bool _onParentActivated(const CEGUI::EventArgs& e);
 
	/* Handles non-tab key activation.
		This will ensure that the next tab key navigation will start from
		the relevant widget. */
	bool _onActivated(const CEGUI::EventArgs& e);
 
	/* Traps the Tab and Shift+Tab key and activates the next or previous 
		widget accordingly. */
	bool _onCharacterKey(const CEGUI::EventArgs& e);
 
	/* Maintains the list of widgets that participate in the tab order */
	std::vector<CEGUI::String> _tabNavigation;
 
	/* Maintains the last known widget to have the focus. */
	std::vector<CEGUI::String>::iterator _lastKnownFocus;
};
 
#endif // _TabNavigation_h_

TabNavigation.cpp

#include "TabNavigation.h"
 
 
/* These provide a visual focus cue when the scheme does not provide one
 *	 for every widget, such as TaharezLook's PushButton */
#define HACKED_FOCUS_GAIN(window) CEGUI::WindowManager::getSingleton().getWindow(window)->setAlpha(1.0f)
#define HACKED_FOCUS_LOSS(window) CEGUI::WindowManager::getSingleton().getWindow(window)->setAlpha(0.8f)
 
/* You can deactivate these hacked focus functions by defining the macro as a comment:
#define HACKED_FOCUS_GAIN(window) //
#define HACKED_FOCUS_LOSS(window) //
 */
 
 
void TabNavigation::setParent(const CEGUI::String& window)
{
	// Parent will feed the tab and shift+tab navigation to the list of monitored widgets
	CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
	wmgr.getWindow(window)->subscribeEvent(CEGUI::Window::EventCharacterKey,	CEGUI::Event::Subscriber(&TabNavigation::_onCharacterKey,		this));
	wmgr.getWindow(window)->subscribeEvent(CEGUI::Window::EventActivated,		CEGUI::Event::Subscriber(&TabNavigation::_onParentActivated,	this));
}
 
void TabNavigation::addWidget(const CEGUI::String& window)
{
	// Add a widget to the list of widget navigation
	CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
 
	wmgr.getWindow(window)->subscribeEvent(CEGUI::Window::EventCharacterKey,	CEGUI::Event::Subscriber(&TabNavigation::_onCharacterKey, this));
	wmgr.getWindow(window)->subscribeEvent(CEGUI::Window::EventActivated,		CEGUI::Event::Subscriber(&TabNavigation::_onActivated,	this));
	HACKED_FOCUS_LOSS(window);
	if(!_tabNavigation.size())
	{
		wmgr.getWindow(window)->activate(); // Activate the first widget by default
		HACKED_FOCUS_GAIN(window);
	}
	_tabNavigation.push_back(window);
	_lastKnownFocus = _tabNavigation.begin(); // Reset the iterator after each modification to the vector
}
 
void TabNavigation::addWidget(const CEGUI::TabControl* tabControl)
{
	// Add every tab buttons of a tab control
	CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
	CEGUI::Window* tabPaneButtons = wmgr.getWindow(tabControl->getName() + "__auto_TabPane__Buttons");
	for(size_t idx = 0; idx < tabPaneButtons->getChildCount(); idx++)
	{
		addWidget(tabPaneButtons->getChildAtIdx(idx)->getName());
	}
}
 
bool TabNavigation::_onParentActivated(const CEGUI::EventArgs& e)
{
	// Parent is being activated, activate the widget with the last known focus
	if(_tabNavigation.size() && _lastKnownFocus != _tabNavigation.end())
	{
		CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
		CEGUI::Window* window = wmgr.getWindow(*_lastKnownFocus);
		if(window)
			window->activate();
	}
	return true;
}
 
bool TabNavigation::_onActivated(const CEGUI::EventArgs& e)
{
	// A focus widget has been activated without tabbing (could be a mouse click)
	CEGUI::String currentlyFocused = static_cast<const CEGUI::WindowEventArgs&>(e).window->getName();
	if(_tabNavigation.size() 
		&& (_lastKnownFocus == _tabNavigation.end()
		|| (*_lastKnownFocus).compare(currentlyFocused))	)
	{
		if(_lastKnownFocus != _tabNavigation.end())
		{
			// These curly braces are IMPORTANT!!!
			HACKED_FOCUS_LOSS(*_lastKnownFocus);
		}
		for(_lastKnownFocus = _tabNavigation.begin(); _lastKnownFocus != _tabNavigation.end(); _lastKnownFocus++)
		{
			if(!(*_lastKnownFocus).compare(currentlyFocused))
			{
				HACKED_FOCUS_GAIN(currentlyFocused);
				return true;
			}
		}
		// Can't figure out who has the focus
		_lastKnownFocus = _tabNavigation.begin();
		HACKED_FOCUS_GAIN(*_lastKnownFocus);
	}
	return true;
}
 
bool TabNavigation::_onCharacterKey(const CEGUI::EventArgs& e)
{
	// Handle Tab (next) and Shift+Tab (previous) widget navigation
	assert(_tabNavigation.size() && "Don't simply call setParent(), also call addWidget()");
	if(static_cast<const CEGUI::KeyEventArgs&>(e).codepoint == 9) // Tab or Shift+Tab
	{
		// Identify who currently has the focus
		CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
		std::vector<CEGUI::String>::iterator itFocus, itCurrent;
		if(_lastKnownFocus != _tabNavigation.end() && wmgr.getWindow(*_lastKnownFocus)->isActive())
		{
			// The last known focus is still in focus
			itCurrent = _lastKnownFocus;
		}
		else
		{
			// Figure out who (if anyone) has the focus
			for(itCurrent = _tabNavigation.begin(); itCurrent != _tabNavigation.end(); itCurrent++)
			{
				if(wmgr.getWindow(*itCurrent)->isActive())
				{
					// Found who has the focus
					break;
				}
			}
			if(itCurrent == _tabNavigation.end())
			{
				// We did not find who had the focus
				// Someone not in our list of monitored widgets has STOLEN the focus!
				// Use the last known focus or, if that's invalid, the first widget
				itCurrent = _lastKnownFocus != _tabNavigation.end() ? _lastKnownFocus : _tabNavigation.begin();
			}
		}
 
		// Change the focus
		itFocus = itCurrent; // The search starts from the currently focused
		CEGUI::Window* newWidget = 0;
		do
		{
			if(static_cast<const CEGUI::KeyEventArgs&>(e).sysKeys & CEGUI::Shift)
			{
				// Search previous
				if(itFocus == _tabNavigation.begin())
					itFocus = --_tabNavigation.end();
				else
					itFocus--;
			}
			else
			{
				// Search next
				itFocus++;
				if(itFocus == _tabNavigation.end())
					itFocus = _tabNavigation.begin();
			}
			newWidget = wmgr.getWindow(*itFocus);
			if(newWidget->isVisible() && !newWidget->isDisabled())
			{
				// We have found a valid focus target
				_lastKnownFocus = itFocus;
				break;
			}
			newWidget = 0;
		} while(itFocus != itCurrent); // Iterate while we're on a different widget
 
		if(newWidget)
		{
			// Remove the focus from this widget
			HACKED_FOCUS_LOSS(*itCurrent);
 
			// Give the focus to this widget
			newWidget->activate();
			HACKED_FOCUS_GAIN(newWidget->getName());
			return true;
		}
	}
	return false;
}

TabNavigation_demo.h

#ifndef _WidgetFocus_Demo_h_
#define _WidgetFocus_Demo_h_
 
#include "CEGuiSample.h"
#include "CEGUI.h"
#include "TabNavigation.h"
 
class DemoSample : public CEGuiSample
{
public:
    bool initialiseSample()
	{
		using namespace CEGUI;
		try
		{
			// Retrieve the window manager
			WindowManager& winMgr = WindowManager::getSingleton();
 
			// Load the TaharezLook scheme and set up the default mouse cursor and font
			SchemeManager::getSingleton().loadScheme("TaharezLook.scheme");
			System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
			if(!FontManager::getSingleton().isFontPresent("Commonwealth-10"))
				FontManager::getSingleton().createFont("Commonwealth-10.font");
 
			// Set the GUI Sheet
			Window* sheet = winMgr.createWindow("DefaultWindow", "root_wnd");
			System::getSingleton().setGUISheet(sheet);
 
			// Load a layout
			Window* guiLayout = winMgr.loadWindowLayout("TabNavigation.layout");
			sheet->addChildWindow(guiLayout);
 
 
			/* TabNavigation-specific code */
			navMainWindow.setParent("TabOrder/MainWindow");
			navMainWindow.addWidget("TabOrder/MainWindow/Field_1");
			navMainWindow.addWidget("TabOrder/MainWindow/Field_2");
			navMainWindow.addWidget("TabOrder/MainWindow/Field_3");
 
			navSecondaryWindow.setParent("TabOrder/SecondaryWindow");
			navSecondaryWindow.addWidget("TabOrder/SecondaryWindow/Field_1");
			navSecondaryWindow.addWidget("TabOrder/SecondaryWindow/Field_2");
			navSecondaryWindow.addWidget("TabOrder/SecondaryWindow/Field_3");
		}
		catch(Exception &e)
		{
			#if defined( __WIN32__ ) || defined( _WIN32 )
				MessageBox(NULL, e.getMessage().c_str(), "Error initializing the demo", MB_OK | MB_ICONERROR | MB_TASKMODAL);
			#else
				std::cerr << "Error initializing the demo:" << e.getMessage().c_str() << "\n";
			#endif
		}
 
		return true;
	}
 
    void cleanupSample(void)
	{
	}
private:
	TabNavigation navMainWindow;
	TabNavigation navSecondaryWindow;
};
 
 
#endif // _WidgetFocus_Demo_h_

TabNavigation.layout

<?xml version="1.0" encoding="UTF-8"?>
 
<GUILayout >
    <Window Type="DefaultWindow" Name="TabOrder" >
        <Property Name="InheritsAlpha" Value="False" />
        <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
        <Property Name="UnifiedAreaRect" Value="{{0,0},{0,0},{1,0},{1,0}}" />
        <Window Type="TaharezLook/FrameWindow" Name="TabOrder/MainWindow" >
            <Property Name="Text" Value="Main Window" />
            <Property Name="TitlebarFont" Value="Commonwealth-10" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="TitlebarEnabled" Value="True" />
            <Property Name="UnifiedAreaRect" Value="{{0.0296875,0},{0.0583334,0},{0.43125,0},{0.468749,0}}" />
            <Window Type="TaharezLook/Editbox" Name="TabOrder/MainWindow/Field_1" >
                <Property Name="Text" Value="Field 1" />
                <Property Name="MaxTextLength" Value="1073741823" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.0719849,0},{0.19543,0},{0.905642,0},{0.354059,0}}" />
            </Window>
            <Window Type="TaharezLook/Button" Name="TabOrder/MainWindow/Field_2" >
                <Property Name="Text" Value="Field 2" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.071985,0},{0.413199,0},{0.905642,0},{0.663199,0}}" />
            </Window>
            <Window Type="TaharezLook/Spinner" Name="TabOrder/MainWindow/Field_3" >
                <Property Name="StepSize" Value="1" />
                <Property Name="CurrentValue" Value="0" />
                <Property Name="MaximumValue" Value="32767" />
                <Property Name="MinimumValue" Value="-32768" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.071985,0},{0.711169,0},{0.905642,0},{0.961169,0}}" />
            </Window>
        </Window>
        <Window Type="TaharezLook/FrameWindow" Name="TabOrder/SecondaryWindow" >
            <Property Name="Font" Value="Commonwealth-10" />
            <Property Name="Text" Value="Secondary Window" />
            <Property Name="TitlebarFont" Value="Commonwealth-10" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="TitlebarEnabled" Value="True" />
            <Property Name="UnifiedAreaRect" Value="{{0.454688,0},{0.0583334,0},{0.856251,0},{0.468749,0}}" />
            <Window Type="TaharezLook/Editbox" Name="TabOrder/SecondaryWindow/Field_1" >
                <Property Name="Font" Value="Commonwealth-10" />
                <Property Name="Text" Value="Field 1" />
                <Property Name="MaxTextLength" Value="1073741823" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.071985,0},{0.19543,0},{0.905642,0},{0.354059,0}}" />
            </Window>
            <Window Type="TaharezLook/Button" Name="TabOrder/SecondaryWindow/Field_2" >
                <Property Name="Font" Value="Commonwealth-10" />
                <Property Name="Text" Value="Field 2" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.071985,0},{0.413199,0},{0.905642,0},{0.663199,0}}" />
            </Window>
            <Window Type="TaharezLook/Spinner" Name="TabOrder/SecondaryWindow/Field_3" >
                <Property Name="Font" Value="Commonwealth-10" />
                <Property Name="StepSize" Value="1" />
                <Property Name="CurrentValue" Value="0" />
                <Property Name="MaximumValue" Value="32767" />
                <Property Name="MinimumValue" Value="-32768" />
                <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                <Property Name="UnifiedAreaRect" Value="{{0.071985,0},{0.711169,0},{0.905642,0},{0.961169,0}}" />
            </Window>
        </Window>
    </Window>
</GUILayout>