==============
Event handling
==============
  
Overview
========

The ``HEventManager`` class receives and dispatches events from one object to another. By registering objects derived from a specific ``HEventListener`` class with the ``EventManager``, it is possible to route one event (which is essentially a function call) to multiple recipients. This is useful in any situation that requires events to be received by more than one object and/or in which the potential recipients of an event are not known in advance.


Creating a new Listener type
============================

MVO already uses a number of predefined ``EventListeners`` for mouse input, animation events and screen updates. However, it is easy to create a new Listener type. 

Here is an example::

	class MVO_API HUpdateListener : public HEventListener
	{
		public:
			// A set of macros are used to define the functions for the various listener types
			// HLISTENER_SETUP_FUNCTIONS() needs to be called with the name of the class and sets
			// up the correct constructor, etc.
			HLISTENER_SETUP_FUNCTIONS_STRING(HUpdateListener)

			// Each function of the Event Listener needs to be declared via a macro which are defined in HEventListener.h
			// The first macro parameter is the class name, the second parameter is the function name
			// the following parameters are the type of the parameter and it's name (in this case for two parameters)
			HLISTENER_FUNCTION_2(HUpdateListener, UpdateEvent, bool, antialias, bool, forceUpdate)
	};

  
Event registration
==================

Each object that is interested in receiving events (in this case Update related) needs to subclass from this Event Listener and must implement at least the functions that it is interested in::

	class MVO_API HBaseView : public HUpdateListener
	{
		.
		.
		int UpdateEvent(bool antialias = false, bool forecUpdate = false);
		.
		.
	}


	int HBaseView::UpdateEvent(bool antialias, bool forceUpdate)
	{
		Update(antialias, forceUpdate);

		// the function can choose to consume the event in which case it will not be propagated to the next
		// recipient or pass it on
		return HLISTENER_PASS_EVENT;
	}

The object must also register itself with the ``EventManager`` it wants to receive the events from. This will be usually be the ``EventManager`` associated with the active model. To register itself with the ``EventManager``, the function ``RegisterHandler`` is called with a pointer to the object that needs to be registered, the type of event that this object should be registered for (one object can derive from multiple event listeners) and a constant indicating the priority with which this object receives this event type. A higher priority means that this object will "see" the event before another object with a lower priority.

	m_pModel->GetEventManager()->RegisterHandler(this, HUpdateListener::GetType(), HLISTENER_PRIORITY_NORMAL);

  
Creating a new event
====================

To create an event from anywhere inside the application, the ``HLISTENER_EVENT`` macro should be used. The first parameter is the event listener that this event belongs to, the second parameter is a pointer to the Event Manager and the third parameter is the function call itself::

	HLISTENER_EVENT(HUpdateListener, m_pHView->GetEventManager(), UpdateEvent())

When executed, the code behind this macro will inject an event into the event queue.


HEventInfo
----------

Mouse clicks and key presses are some of the most common user events. In HOOPS/MVO, the ``HMouseListener`` class defines a set of interfaces that handles these events. The methods specified in ``HMouseListener`` all take one parameter, an ``HEventInfo`` object. The ``HEventInfo`` class encapsulates information like mouse pointer position and various keyboard states and is platform independent.

When your application receives a mouse or keyboard event from the system, the raw data is likely to be in a platform specific format.  In your method for handling the event, you will need to repackage this information into an ``HEventInfo`` object before passing it on. For example, to process a mouse event, create an instance of ``HEventInfo`` and initialize it using ``HEventInfo::SetPoint``, which requires the following parameters:

* Type of event
* x and y locations in pixel space where the event occurred
* :Flags associated with the event

The following sample code shows how you might handle a left mouse button double click event::

	void MyHoopsView::OnLButtonDblClk(UINT nFlags, CPoint point) 
	{
		if (m_pHView)
		{
			HEventInfo	event(m_pHView);
			event.SetPoint(HE_LButtonDblClk, point.x, point.y, MapFlags(nFlags));
			HLISTENER_EVENT(HMouseListener, m_pHView->GetEventManager(), OnLButtonDblClk(event))
		}
	}

``MapFlags`` is a utility function that maps the UI-specific event-state flags to platform-independent HOOPS/MVO flags. It is part of both the HOOPS/MFC and HOOPS/QT integrations. Please refer to ``CHoopsView::MapFlags`` and ``HQWidget::MapFlags`` for more details. 

Note that all ASCII characters are handled. However, only certain special keys are detected in a platform-independent way. These keys are listed below:

* Ctrl
* Alt
* Shift
* Up arrow
* Down arrow
* Left arrow
* Right arrow

Other keys may be detected, however, the integer value of those key presses is not guaranteed to be equivalent across all platforms. If you require the use of unsupported keys, we recommend that you set your own value for the key when building the event, then test for that value when processing the event in your operator. Example::

	int myKeyValue = 282; // "282" will be the custom key value
	event.SetKey(HE_KeyDown, myKeyValue, 1, 0); // setting the custom value

	HLISTENER_EVENT(HMouseListener, m_pHView->GetEventManager(), OnKeyDown(event)); // inject the event


3D Mouse and joystick events
============================

In addition to traditional events like mouse movement and key presses, HOOPS/MVO also provides a predefined event listener for feedback from 3D devices such as 3D mice and joysticks via the ``HJoyStickListener`` class.  

To utilize ``HJoyStickListener``'s capabilities, a class must inherit from this event listener. During the initialization of the class, you need to register with the Event Manager to receive notice of the joystick events. The class must also implement ``HJoyStickListener``'s two main methods: ``OnJoyStickRotation``, ``OnJoyStickTranslation``, and ``OnJoyStickButton``. 


The ``HJoyStickListener::OnJoyStickRotation`` is called whenever a rotation event occurs with the 3D mouse or joystick.  When this method is called, an ``HEventInfo`` object is passed as a parameter. You can use the ``HEventInfo::GetJoystickRotation`` method to access the current rotation of x, y and z axes.  Note that this information is raw data from the device, we recommend that you invest some time in fine tuning how your application wishes to interpret this data.  

When a translation event occurs with the joystick or 3D mouse, the ``OnJoyStickTranslation`` method is called and an ``HEventInfo`` object is passed. Using the ``HEventInfo::GetJoystickTranslation`` method, you can access the current translation vector.  As with the rotation event, the information for the translation vector is also raw data from the device.  We suggest that you determine how you want to interpret this information such that it represents the best experience for your user.

If a button is pressed, the ``OnJoyStickButton`` method is called. In the ``HEventInfo`` object, you can determine which 3D mouse button was pressed by calling ``HEventInfo::GetFlags()``. HOOPS supports up to eight mouse buttons from ``MVO_JOYSTICK_BUTTON_1`` to ``MVO_JOYSTICK_BUTTON_8``.

The following sample code shows how a class would access and utilize the events provided by the ``HJoyStickListener`` event listener on the Microsoft Windows platform. Example::

	#include "HEventInfo.h"
	#include "HEventListener.h"

	class MyJoyStickHandler : public HJoyStickListener
	{
	public:
		MyJoyStickHandler(HBaseView* view);

		//The method that receives joystick rotation events
		int OnJoyStickRotation( HEventInfo &hevent);
		
		//The method that receives joystick translation events
		int OnJoyStickTranslation( HEventInfo &hevent);

		//The method that receives joystick button events
		int OnJoyStickButton(HEventInfo &hevent){

	};
	 
	MyJoyStickHandler::MyJoyStickHandler(HBaseView* view )
	{
		//Registering for the HJoyStickListener Events
		view->GetEventManager()->RegisterHandler((HJoyStickListener *)this, HJoyStickListener::GetType(), HLISTENER_PRIORITY_NORMAL);    
	}

	int MyJoyStickHandler::OnJoyStickRotation( HEventInfo &hevent)
	{
		//Getting the rotation information
		HPoint rotate = hevent.GetJoystickRotation();

		HC_Open_Segment_By_Key(hevent.GetView()->GetSceneKey());
			//Applying the x, y and z axes rotation information to pan and rotate the camera
			HC_Pan_Camera(-rotate.z /2048.0, rotate.x / 2048.0);
			HC_Roll_Camera(-rotate.y / 2048.0);
			hevent.GetView()->Update();
		HC_Close_Segment();
		return 0;
	}

	int MyJoyStickHandler::OnJoyStickTranslation( HEventInfo &hevent)
	{
		//Getting the translation vector
		HPoint xlate = hevent.GetJoystickTranslation();
		float center[3];
		float radius, scene_scale;

		HC_Open_Segment_By_Key(hevent.GetView()->GetSceneKey());
			HC_Show_Bounding_Sphere(center, &radius);
			scene_scale = radius / 81456.38;
			//Applying the translation vector to move the camera
			HC_Dolly_Camera(xlate.x / 2.5 * scene_scale, xlate.y / 2.5 * scene_scale, xlate.z / 2.5* scene_scale);
			hevent.GetView()->Update();
		HC_Close_Segment();
		return 0;
	}

	int MyJoyStickHandler::OnJoyStickButton(HEventInfo &hevent){
		TCHAR temp[MVO_BUFFER_SIZE];
		if (hevent.GetFlags() == MVO_JOYSTICK_BUTTON_1){
			_stprintf(temp, _T("3D Mouse Button 1"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_2){
			_stprintf(temp, _T("3D Mouse Button 2"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_3){
			_stprintf(temp, _T("3D Mouse Button 3"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_4){
			_stprintf(temp, _T("3D Mouse Button 4"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_5){
			_stprintf(temp, _T("3D Mouse Button 5"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_6){
			_stprintf(temp, _T("3D Mouse Button 6"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_7){
			_stprintf(temp, _T("3D Mouse Button 7"));
			AfxMessageBox(temp);
		}
		else if(hevent.GetFlags() == MVO_JOYSTICK_BUTTON_8){
			_stprintf(temp, _T("3D Mouse Button 8"));
			AfxMessageBox(temp);
		}
		return 0;
	}

Since HOOPS/MVO is platform independent, your application must implement code to capture joystick events from the system and dispatch these events to HOOPS/MVO. For example, if your application runs on Microsoft Windows, a convenient method for managing 3D mouse/joystick events is via the DirectInput API. The sample code below is from the MFC project found in *<hoops>/Dev_Tools/hoops_mfc/source/hoops_mfcu_mgk_dinput_[version].vcxproj*. It shows how you can implement an integration between DirectInput and HOOPS/MVO for 3D mouse/joystick events with the following steps:

#. Initialize the connection with DirectInput.
#. Poll for 3D mouse/joystick information in your application's timer function.
#. Get 3D mouse/joystick states from DirectInput.
#. Package the 3D mouse/joystick states into an instance of ``HEventInfo``.
#. Dispatch the event using the ``HLISTENER_EVENT`` macro.

Example code::

	/* Initializes the DirectInput variables.*/
	HRESULT CHoopsView::InitDI8( void )
	{
		HRESULT hr;
		g_nDevices = 0;

		// Register with the DirectInput subsystem and get a pointer to a IDirectInput interface we can use.
		// Create a DInput object
		if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
											 IID_IDirectInput8, (VOID**)&(dev_handle->g_pDI), NULL ) ) )
		{
			m_bUsingDI8Joystick = false;
			return hr;
		}

		// Look for all 3Dx HID devices.
		if( FAILED( hr = dev_handle->g_pDI->EnumDevices( DIDEVTYPE_HID, 
											 EnumDevicesCB,
											 this, DIEDFL_ATTACHEDONLY ) ) ){
			m_bUsingDI8Joystick = false;
			return hr;
		}

		// Make sure we got an HID device
		if( g_nDevices == 0 )
		{
			m_bUsingDI8Joystick = false;
			return S_OK;
		}
		
		/* Set the data format to "simple joystick" - a predefined data format 
		 * A data format specifies which controls on a device we are interested in,
		 * and how they should be reported. This tells DInput that we will be
		 * passing a DIJOYSTATE structure to IDirectInputDevice::GetDeviceState().
		 * This is a fine format to use for the 3Dx Devoce as it has 6 DOF and upto
		 * 32 buttons.
		 */
		if( FAILED( hr = g_pDevice->pUsbHidDevice->SetDataFormat( &c_dfDIJoystick ) ) ){
			m_bUsingDI8Joystick = false;
			return hr;
		}

		/* Set the cooperative level to let DInput know how this device should
		 * interact with the system and with other DInput applications.
		 * You probably don't want the dialog box to be the target of the events.*/
		if( FAILED( hr = g_pDevice->pUsbHidDevice->SetCooperativeLevel( AfxGetApp()->m_pMainWnd->GetSafeHwnd(), 
				   DISCL_EXCLUSIVE | DISCL_FOREGROUND    // normal use:  get events when have focus
				 //DISCL_NONEXCLUSIVE | DISCL_BACKGROUND /* get events all the time */
				 ) ) ){
			m_bUsingDI8Joystick = false;
			return hr;
		}

		/* Determine how many axis the UsbHidDevice has (so we don't error out setting
		 * properties for unavailable axis)*/
		g_pDevice->diDevCaps.dwSize = sizeof(DIDEVCAPS);
		if ( FAILED( hr = g_pDevice->pUsbHidDevice->GetCapabilities(&g_pDevice->diDevCaps) ) ){
			m_bUsingDI8Joystick = false;
			return hr;
		}

		/* Enumerate the axes of the UsbHidDevice and set the range of each axis and
		 * make sure the axes are set to ABSOLUTE mode. */
		if ( FAILED( hr = g_pDevice->pUsbHidDevice->EnumObjects( EnumAxesCB, 
			(VOID*)g_pDevice, DIDFT_AXIS ) ) ){
			m_bUsingDI8Joystick = false;
			return hr;
		}
		return S_OK;
	}

	/*Callback function for enumerating the axes on a UsbHidDevice*/
	BOOL CALLBACK EnumAxesCB( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )
	{
		DIDeviceInfo *pDIDeviceInfo = (DIDeviceInfo *)pContext;


		/*
		 * Recent 3Dx USB device descriptors indicate that the device axes return relative data
		 * even though the device is really an absolute device.
		 * HID ignores this, but apparently DI uses it.
		 * Changing the axis handling to return RELATIVE values gives us back what we want (displacement
		 * values rather than accumulated values).  If older USB devices were to be supported, this handling
		 * would have to be changed to DIPROPAXISMODE_ABS.  
		 *
		 * In this mode, DI sets the notification event even though the values haven't changed.
		 */
		DIPROPDWORD dipdw;
		dipdw.diph.dwSize       = sizeof(DIPROPDWORD); 
		dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
		dipdw.diph.dwHow        = DIPH_DEVICE; 
		dipdw.diph.dwObj        = 0; // set for whole device not an axis (really only needs to be done once)
		dipdw.dwData            = DIPROPAXISMODE_REL;

		if( FAILED( pDIDeviceInfo->pUsbHidDevice->SetProperty( DIPROP_AXISMODE, &dipdw.diph ) ) )
			return DIENUM_STOP;

		/* Setting up the buffered input for the mouse buttons*/
		dipdw.dwData            = 10;

		if( FAILED( pDIDeviceInfo->pUsbHidDevice->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) )
			return DIENUM_STOP;
		
		return DIENUM_CONTINUE;
	}


	/*The timer function that polls the for joystick events*/
	void CHoopsView::OnTimer(UINT_PTR nIDEvent)
	{
		if (m_pHView)
		{
			HEventInfo	event(m_pHView);
			HBaseOperator *op = m_pHView->GetCurrentOperator();
			
			if(m_bUsingDI8Joystick)
				this->UpdateDI8State(0);
				
			if (op)
			{
				event.SetTimer(HE_Timer, nIDEvent);
				op->OnTimer(event);
			}
			else
				CView::OnTimer(nIDEvent);
		}
	}

	/* Get the input device's state.  Dispatches an JoystickButton event if any of the button flags are set.*/
	HRESULT CHoopsView::UpdateDI8State( unsigned long devIndex )
	{
		HRESULT     hr;
		DIJOYSTATE  js;           // DInput UsbHidSpaceball state 
		DIDeviceInfo dinfo = *g_pDevice;

		if( NULL == dinfo.pUsbHidDevice ) 
			return S_OK;

		/* 
		 * Poll the device to read the current state.  This should not be necessary
		 * but is advised by DI documentation and should not take a significant amount
		 * of time.  (This could be where we get the double vector events.)
		 */
		hr = dinfo.pUsbHidDevice->Poll(); 
		if( FAILED(hr) )  
		{
			//OutputDebugString("Poll failed\n");

			// DInput is telling us that the input stream has been
			// interrupted. We aren't tracking any state between polls, so
			// we don't have any special reset that needs to be done. We
			// just re-acquire and try again.
			hr = dinfo.pUsbHidDevice->Acquire();
			while( hr == DIERR_INPUTLOST ) 
				hr = dinfo.pUsbHidDevice->Acquire();

			// hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
			// may occur when the app is minimized or in the process of 
			// switching, so just try again later 
			return S_OK; 
		}

		// Get the input's device state
		if( FAILED( hr = dinfo.pUsbHidDevice->GetDeviceState( sizeof(DIJOYSTATE), &js ) ) )
		{
			//OutputDebugString("GetDeviceState failed\n");
			return hr; // The device should have been acquired during the Poll()
		}

		// Display UsbHidDevice state to dialog
		HandleAxesState( this, js );

		DIDEVICEOBJECTDATA rgdod[10]; 
		DWORD dwItems = 10; 
		hr = dinfo.pUsbHidDevice->GetDeviceData( 
			sizeof(DIDEVICEOBJECTDATA), 
			rgdod, 
			&dwItems, 
			0); 
		if (SUCCEEDED(hr)) { 
			// dwItems = Number of elements read (could be zero).
			if (hr == DI_BUFFEROVERFLOW) { 
			// Buffer had overflowed. 
			} 
			for(int i = 0; i < (int)dwItems; i++){
				HEventInfo	button1_event(this->m_pHView);
				button1_event.SetType(HE_JoyStickButton);

				switch(rgdod[i].dwOfs){
					case DIJOFS_BUTTON0:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_1);
						break;
					case DIJOFS_BUTTON1:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_2);
						break;
					case DIJOFS_BUTTON2:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_3);
						break;
					case DIJOFS_BUTTON3:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_4);
						break;
					case DIJOFS_BUTTON4:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_5);
						break;
					case DIJOFS_BUTTON5:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_6);
						break;
					case DIJOFS_BUTTON6:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_7);
						break;
					case DIJOFS_BUTTON7:
						button1_event.SetFlags(MVO_JOYSTICK_BUTTON_8);
						break;
				}

				if(button1_event.GetFlags() != 0)
					//Dispatching a button event
					HLISTENER_EVENT(HJoyStickListener, this->m_pHView->GetEventManager(), OnJoyStickButton(button1_event))
			}
		} 

		return S_OK;
	}


	/* This method reads the joystick state and generates the HOOPS/MVO Joystick Rotate 
	   and Translate events.*/
	void HandleAxesState(CHoopsView* view, DIJOYSTATE js )

	{
		// Knowing the peculiar characteristics of the USB/HID/DI implementation, combine all 6 DOFs
		CombineDataStreams(view, js );

		HEventInfo	translate_event(view->m_pHView);
		translate_event.SetType(HE_JoyStickTranslate);
		translate_event.SetWorldPos(HE_JoyStickTranslate, HPoint(-js.lX, -js.lZ, -js.lY), 0);
		HLISTENER_EVENT(HJoyStickListener, view->m_pHView->GetEventManager(), OnJoyStickTranslation(translate_event))

		HEventInfo	rotate_event(view->m_pHView);
		rotate_event.SetType(HE_JoyStickRotate);
		rotate_event.SetWorldPos(HE_JoyStickRotate, HPoint(js.lRx, js.lRy, js.lRz), 0);
		HLISTENER_EVENT(HJoyStickListener, view->m_pHView->GetEventManager(), OnJoyStickRotation(rotate_event))

	}

For a more comprehensive look at the implementation, please see the HOOPS/MFC project found in *<hoops>/Dev_Tools/hoops_mfc/source/hoops_mfcu_mgk_[version].vcxproj*. The DirectInput integration is enabled with the ``DIRECT_INPUT_SUPPORT`` flag.


Timer events
============

The ``HTManager`` object in MVO provides a centralized timing service for MVO and allows the developer to register operators which will receive time-based function callbacks. Using timer-based operators allows developers to create operators which are interruptable and can be forced to occur over a user-determined span of time. Timer-based events are used extensively for animations and playback sequences.

To best demonstrate how to create a timer-based operator, we will review the ``HTCObjectRotate`` operator in MVO. This was added to MVO as a reference implementation. It allows you to select an object and get it to rotate about its centroid for 10 seconds and then stop. At any point, selecting on the object again will stop it rotating. Since the operator is timer based you can continue to use other operators to navigate about the scene or select any of the timer objects to stop before the 10-second rotation has completed.

For any timer-based operators you must derive from the HTClient class. Since we are implementing an operator, we will also inherit the ``HBaseOperator`` class. Note, timer-based events do not have to be operators, for example the smooth camera transition mechanism in MVO is also based on a timer event.

Let's look at our ``HTCObjectRotate`` class::

	class MVO_API HTCObjectRotate : public HBaseOperator, public HTClient
	{
	public:

		HTCObjectRotate (HBaseView* view, int DoRepeat=0, int DoCapture=1);
		~HTCObjectRotate();

		// OnLButtonDown is where we do the work of determining which segment to rotate
		int OnLButtonDown (HEventInfo &event);

		// Tick is the function which receives the callbacks at the user defined frequency
		bool Tick( float request_time, float actual_time );

		// Sets a flag indicating whether we should delete *this when the client is UnRegistered
		void SetDeleteOnUnRegister() { m_delete_on_unregister = true; };

		// UnRegister unregisters the timer client from HTManager and deletes *this if flag set  
		void UnRegister ();

		// OnSignal is called by the HBaseView destructor, allows the Client to do any necessary
		// cleanup in the event of a unforseen window shutdown 
		bool OnSignal( int signal );
	};

The first thing you need to do is register your timer client with ``HTManager``, the timer manager object in MVO. This work is done in the ``HTCObjectRotate::OnLButtonDown``. When a ``LButtonEvent`` occurs this operator either registers or removes a timer client from ``HTManager``::

	int HTCObjectRotate::OnLButtonDown(HEventInfo &event)
	{
		HC_KEY key;
		HTCObjectRotate *op;

		// this never occurs
		if(m_state == HTCOR_Animating)
		{
			UnRegister();
			return HOP_OK;
		}

		// returns true if an object is selected and in that case
		// returns the key to that object
		if(!GetSelection(event, &key))
			return HOP_OK;

		// determines if the object is already in a timer event by querying 
		// a user value on the object and if it is then it is removed
		// from the HTManager.
		if(SearchAndDestroyHTCOR(key))
			return HOP_OK;

		// this is for dealing with a case in MVO where we're dealing with
		// cutting planes.  You can do work here for any special cases you 
		// may have in your scene graph
		while(SpecialKey(key))
			key = HC_KShow_Owner_By_Key(key);


		// If all is fine from above let's create our Timer client  
		op = new HTCObjectRotate(*this);

		// set it so it gets deleted when the event finishes
		op->SetDeleteOnUnRegister();

		// and then call HTCObject::Animate which does all the work of 
		// registering the client with HTManager
		op->Animate(key);

		return HOP_OK;
	}

``HTC_ObjectRotate::SearchAndDestroy`` simply queries the object to see if a user option is set on it and if it is then it UnRegisters it, effectively stopping the object being animated. Note, we store a pointer to a segment's timer event in the HOOPS database and use the existence of this to determine whether or not the object is currently in a timer event and if so we then use that pointer to unregister it. This is custom to this specific operator and may not be the right approach for your own timer client. You may not need to determine whether or not an object is already in a timer event or you may want to store this information in a separate data structure. We put this information in the HOOPS database for convenience.

Now let's look at ``HTCObjectRotate::Animate``. Before the code below, we moved the selected object into a unique segment and set a user option on it so that ``HTCObjectRotate::SearchAndDestroyHTCOR`` can later determine if it is already in a timer event. You should review the style enums section in the Reference Manual for details on the different style types. Note, you are not guaranteed to receive at your requested rate. However the timer manager will, depending on your style setting, let you know if you have been notified at the requested rate::

	void HTCObjectRotate::Animate(HC_KEY key)
	{
		.
		.

		// Sets the time interval between calls to our implementation of HTClient::Tick(). 
		// The value is in seconds, so a setting of 0.01 means that the timer manager 
		// will try to call HTCObjectRotate::Tick 10 times a second
		SetInterval(0.01f);

		// Setting a periodic style enables us to ensure our rotational
		// position is based solely on time 
		SetStyle(HTCS_PeriodicSkip);

		// tick will be called 0.01 seconds after the client has been
		// registered with the Manager
		SetNextRequest(m_start_time + 0.01f);

		// Here we register a function with the HBaseView object which will get called if 
		// the view object is  destroyed, this will allow us to shutdown the timer event correctly
		m_cookie = m_pView->SetSignalNotify(HSignalDelete, HTCObjectRotate::signal_callback, this);

		// Finally we register the client with the manager
		HDB::GetHTManager()->RegisterClient(this);

		m_pView->Update();
	}

Now that we have registered our client with the timer manager and have set a periodic style, our implementation of ``HTClient::Tick`` will be called with the time at whichit should have been called and also the actual time that the function is being called. This information allows us to implement logic which ensures that the operation occurs in the pre-defined time. In this case, we check if the elapsed time is greater than the duration (which is set as 10 seconds in the constructor). If so, then we unregister the client. Otherwise, we set a modelling matrix on the segment based on the elapsed time::

	bool HTCObjectRotate::Tick( float request_time, float actual_time )
	{
		HPoint axis;
		float halfway, elapsed, angle;

		if( request_time - m_start_time > m_duration ) 
		{
			HC_Open_Segment_By_Key(m_segkey);
			  HC_UnSet_Modelling_Matrix();
			  if( m_original_matrix_valid ) 
				   HC_Append_Modelling_Matrix( m_original_matrix );
		
			HC_Close_Segment();

			UnRegister();
			return true;
		}

		// calculate the angle by which we would like to rotate
		elapsed = actual_time - m_start_time;
		halfway = m_start_time + m_duration/2.0f;

		angle = elapsed / m_duration;

		if( actual_time < halfway )
			angle *= angle * 2.0f;
		else 
		{
			angle = 1.0f - angle;
			angle *= angle;
			angle = 1.0f - 2.0f * angle;
		}
		m_angle = angle * 3.0f * 360.0f;

		HC_Open_Segment_By_Key(m_segkey);
		  axis.x = 1;
		  axis.y = 1;
		  axis.z = 1;
		  HC_UnSet_Modelling_Matrix();
		  RotateSegmentLocally ( m_segkey, &axis, &m_centroid, m_angle );
		  if( m_original_matrix_valid ) 
			HC_Append_Modelling_Matrix( m_original_matrix );

		HC_Close_Segment();

		m_pView->Update();

		return true;

	} //end function Tick

Finally let's look at ``HTCObjectRotate::UnRegister``. It simply tells the manager to remove it from the list of objects receiving timer events and then unsets any user values that may have been previously set on the scene graph. It also unregisters it from the list of objects that should be notified if the HBaseView object is destroyed. If the flag is set, it also deletes the object::

	void HTCObjectRotate::UnRegister()
	{
	HDB::GetHTManager()->UnRegisterClient( this );

	m_state = HTCOR_Resting;

	if( m_pView != NULL ) 
	{
	HC_Open_Segment_By_Key( m_segkey );
	HC_UnSet_One_User_Index( HTC_OBJECT_ROTATE_MAGIC );
	HC_Delete_Segment( "htcor_temp" );
	HC_Close_Segment();

	m_pView->UnSetSignalNotify( m_cookie );
	m_pView->Update();
	}

	m_segkey = -1;
	m_key = -1;

	if( m_delete_on_unregister )
	delete this;
	}


Touch events
============

HOOPS/MVO provides multi-touch support, which can be utilized under supported HOOPS platforms that offer touch interfaces. (For example, iOS)

``HBaseView`` provides a method called ``HBaseView::GetTouchManager``. This returns the ``HTouchManager`` object, which allows for persistent handling of touches across multiple interaction events. ``HTouchManager::AddTouch()`` inserts new touches in the ``HTouchManager`` list, or updates an existing touch instance. ``HTouchManager::GetTouches()`` returns a list of touch instances. Each touch should have a unique identifier which allows ``HTouchManager`` to track touches across events.

For example, let's say four 'touch down' events come in from the OS. The application code would call ``HTouchManager::AddTouch()`` four times. Then, consistent wtih the listener concepts covered above, ``HLISTENER_EVENT`` would get called with the appropriate touch event. Let's look at the application/GUI-level code from the HOOPS iOS demo, which handles the various touch events and dispatches them using ``HLISTENER_EVENT``.

The following is a utility method called ``AddTouch`` which conversts the GUI 'pixel space' coordinate to 'window space' (which is the coorindate system required by all the HOOPS/3DGS selection functions), and then calls ``HTouchManager::AddTouch`` to add the touch to the list of touches::

	-(void)AddTouch: (UITouch *)touch withEvent:(HEventInfo *)event
	{
		CGPoint location = [touch locationInView:self];
			
		HPoint pixelpos(location.x, location.y, 0);
		HPoint windowpos;
							
		HC_Open_Segment_By_Key(m_pHoopsView->GetSceneKey());
		{
			HC_Compute_Coordinates(".", "local pixels",&pixelpos, "local window", &windowpos);
		} 
		HC_Close_Segment();
			
		event->SetWindowPos(HE_LButtonDown,&windowpos,MVO_LBUTTON);

		m_pHoopsView->GetTouchManager()->AddTouch(windowpos.x, windowpos.y,0.0f,[touch tapCount],(MVO_POINTER_SIZED_UINT)touch);
	}

The following is an implementation of a GUI-specific ``touchesBegan`` method. Note the various cases:

* if there is a single touch (i.e. a single finger touches the screen) with a double-tap, it will perform a selection
* if there are 3 touches (i.e. three fingers touch the screen) it will do a FitWorld (aka 'zoom to extents'). 
* otherwise, it will pass all the touch events onto any objects/operators that are 'listening' to the touch events, and the events are dispatched via ``HBaseOperator::OnTouchesDown``::

	- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)evt 
	{
	  
	  NSSet *allTouches = [evt allTouches];
	  HEventInfo event(m_pHoopsView);
	  
	  m_pHoopsView->GetTouchManager()->ClearTouchActivity();
	  if ([allTouches count] == 3)
	  {
		  m_pHoopsView->FitWorld();
		  m_pHoopsView->Update();
		  return;
	  }
	  
	  if ([allTouches count] == 1)
	  {
		  UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];    
		  if ([touch1 tapCount] == 2)
		  {
			  [self selectFromTouch:touch1];
			  return;
		  }
	  } 
	  
	  for (int i=0;i<[allTouches count];i++)
	  {                        
		  UITouch *touch1 = [[allTouches allObjects] objectAtIndex:i];    
		  [self AddTouch:touch1 withEvent:&event];
	  }
	  
	  HLISTENER_EVENT(HMouseListener, m_pHoopsView->GetEventManager(), OnTouchesDown(event));
	}

In addition to the various touch events being properly handled/dispatched in the GUI-specific event handler methods, each touch-enabled MVO ``HBaseOperator`` class  needs to implement the ``HBaseOperator::OnTouchesDown``, ``HBaseOperator::OnTouchesMove``, and ``HBaseOperator::OnTouchesUp`` as require by the operator's specific goals. HOOPS/MVO already includes touch-capable variants of the standard camera operators:

* HOpCameraManipulateTouch
* HOpCameraPanTouch
* HOpCameraZoomTouch
* HOpCameraRollTouch

Each of these MVO operator classes is provided in source code form. This gives you a reference of how you would access the touch information coming in via the ``HEventInfo`` class, if case you wish to create  custom touch-capable operators derived from ``HBaseOperator``.
