aglEventHandler.cpp

00001 
00002 /* Copyright (c) 2007-2009, Stefan Eilemann <eile@equalizergraphics.com> 
00003  *
00004  * This library is free software; you can redistribute it and/or modify it under
00005  * the terms of the GNU Lesser General Public License version 2.1 as published
00006  * by the Free Software Foundation.
00007  *  
00008  * This library is distributed in the hope that it will be useful, but WITHOUT
00009  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00010  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
00011  * details.
00012  * 
00013  * You should have received a copy of the GNU Lesser General Public License
00014  * along with this library; if not, write to the Free Software Foundation, Inc.,
00015  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00016  */
00017 
00018 #include "aglEventHandler.h"
00019 
00020 #include "aglWindow.h"
00021 #include "aglWindowEvent.h"
00022 #include "config.h"
00023 #include "configEvent.h"
00024 #include "global.h"
00025 #include "log.h"
00026 #include "pipe.h"
00027 #include "window.h"
00028 
00029 #include <eq/net/global.h>
00030 #include <eq/base/fileSearch.h>
00031 
00032 #ifdef EQ_USE_MAGELLAN
00033 #  include <3DconnexionClient/ConnexionClientAPI.h>
00034 #endif
00035 
00036 using namespace eq::base;
00037 using namespace std;
00038 
00039 namespace eq
00040 {
00041 AGLEventHandler::AGLEventHandler( AGLWindowIF* window )
00042         : _window( window )
00043         , _eventHandler( 0 )
00044         , _eventDispatcher( 0 )
00045         , _lastDX( 0 )
00046         , _lastDY( 0 )
00047 {
00048     const WindowRef carbonWindow = window->getCarbonWindow();
00049     if( !carbonWindow )
00050     {
00051         EQWARN << "Can't add window without native Carbon window to AGL event "
00052                << "handler" << endl;
00053         return;
00054     }
00055     
00056     Global::enterCarbon();
00057     EventHandlerUPP eventHandler = NewEventHandlerUPP( 
00058         eq::AGLEventHandler::_handleEventUPP );
00059     EventTypeSpec   eventType[]    = {
00060         { kEventClassWindow,   kEventWindowBoundsChanged },
00061         { kEventClassWindow,   kEventWindowZoomed },
00062         { kEventClassWindow,   kEventWindowUpdate },
00063         { kEventClassWindow,   kEventWindowDrawContent },
00064         { kEventClassWindow,   kEventWindowClosed },
00065         { kEventClassWindow,   kEventWindowHidden },
00066         { kEventClassWindow,   kEventWindowCollapsed },
00067         { kEventClassWindow,   kEventWindowShown },
00068         { kEventClassWindow,   kEventWindowExpanded },
00069         { kEventClassMouse,    kEventMouseMoved },
00070         { kEventClassMouse,    kEventMouseDragged },
00071         { kEventClassMouse,    kEventMouseDown },
00072         { kEventClassMouse,    kEventMouseUp },
00073         { kEventClassKeyboard, kEventRawKeyDown },
00074         { kEventClassKeyboard, kEventRawKeyUp },
00075         { kEventClassKeyboard, kEventRawKeyRepeat }
00076     };
00077 
00078     InstallWindowEventHandler( carbonWindow, eventHandler, 
00079                                sizeof( eventType ) / sizeof( EventTypeSpec ),
00080                                eventType, this, &_eventHandler );
00081 
00082     const Pipe* pipe = window->getPipe();
00083     if( pipe->isThreaded( ))
00084     {
00085         EQASSERT( GetCurrentEventQueue() != GetMainEventQueue( ));
00086 
00087         // dispatches to pipe thread queue
00088         EventHandlerUPP eventDispatcher = NewEventHandlerUPP( 
00089             eq::AGLEventHandler::_dispatchEventUPP );
00090         EventQueueRef target = GetCurrentEventQueue();
00091 
00092         InstallWindowEventHandler( carbonWindow, eventDispatcher, 
00093                                    sizeof( eventType ) / sizeof( EventTypeSpec),
00094                                    eventType, target, &_eventDispatcher );
00095     }
00096     else
00097         _eventDispatcher = 0;
00098 
00099     Global::leaveCarbon();
00100 
00101     EQINFO << "Installed event handlers for carbon window " << carbonWindow
00102            << endl;
00103 }
00104 
00105 AGLEventHandler::~AGLEventHandler()
00106 {
00107     Global::enterCarbon();
00108     if( _eventDispatcher )
00109     {
00110         RemoveEventHandler( _eventDispatcher );
00111         _eventDispatcher = 0;
00112     }
00113     if( _eventHandler )
00114     {
00115         RemoveEventHandler( _eventHandler );
00116         _eventHandler = 0;
00117     }
00118     Global::leaveCarbon();
00119 }
00120 
00121 pascal OSStatus AGLEventHandler::_dispatchEventUPP( 
00122     EventHandlerCallRef nextHandler, EventRef event, void* userData )
00123 {
00124     EventQueueRef target = static_cast< EventQueueRef >( userData );
00125     
00126     if( GetCurrentEventQueue() != target )
00127     {
00128 #if 0 // some events pop up on pipe thread queues...
00129         EQASSERTINFO( GetCurrentEventQueue() == GetMainEventQueue(),
00130                       "target " << target << " current " << 
00131                       GetCurrentEventQueue() << " main " << 
00132                       GetMainEventQueue( ));
00133 #endif
00134         PostEventToQueue( target, event, kEventPriorityStandard );
00135     }
00136     return CallNextEventHandler( nextHandler, event );
00137 }
00138 
00139 pascal OSStatus AGLEventHandler::_handleEventUPP( 
00140     EventHandlerCallRef nextHandler, EventRef event, void* userData )
00141 {
00142     AGLEventHandler* handler = static_cast< AGLEventHandler* >( userData );
00143     AGLWindowIF*     window  = handler->_window;
00144 
00145     if( GetCurrentEventQueue() == GetMainEventQueue( )) // main thread
00146     {
00147         const Pipe* pipe = window->getPipe();
00148         if( !pipe->isThreaded( ))
00149             // non-threaded window, handle from main thread
00150             handler->_handleEvent( event );
00151 
00152         return CallNextEventHandler( nextHandler, event );
00153     }
00154 
00155     handler->_handleEvent( event );
00156     return noErr;
00157 }
00158 
00159 bool AGLEventHandler::_handleEvent( EventRef event )
00160 {
00161     switch( GetEventClass( event ))
00162     {
00163         case kEventClassWindow:
00164             return _handleWindowEvent( event );
00165         case kEventClassMouse:
00166             return _handleMouseEvent( event );
00167         case kEventClassKeyboard:
00168             return _handleKeyEvent( event );
00169         default:
00170             EQINFO << "Unknown event class " << GetEventClass( event ) << endl;
00171             return false;
00172     }
00173 }
00174 
00175 bool AGLEventHandler::_handleWindowEvent( EventRef event )
00176 {
00177     AGLWindowEvent windowEvent;
00178     windowEvent.carbonEventRef = event;
00179     Window* const window       = _window->getWindow();
00180 
00181     Rect      rect;
00182     WindowRef carbonWindow = _window->getCarbonWindow();
00183 
00184     GetWindowBounds( carbonWindow, kWindowContentRgn, &rect );
00185     windowEvent.resize.x = rect.top;
00186     windowEvent.resize.y = rect.left;
00187     windowEvent.resize.h = rect.bottom - rect.top;
00188     windowEvent.resize.w = rect.right  - rect.left;
00189 
00190     if( window->getIAttribute( Window::IATTR_HINT_DECORATION ) != OFF )
00191         windowEvent.resize.y -= EQ_AGL_MENUBARHEIGHT;
00192 
00193     switch( GetEventKind( event ))
00194     {
00195         case kEventWindowBoundsChanged:
00196         case kEventWindowZoomed:
00197             windowEvent.type = Event::WINDOW_RESIZE;
00198             break;
00199 
00200         case kEventWindowUpdate:
00201             BeginUpdate( carbonWindow );
00202             EndUpdate( carbonWindow );
00203             // no break;
00204         case kEventWindowDrawContent:
00205             windowEvent.type = Event::WINDOW_EXPOSE;
00206             break;
00207 
00208         case kEventWindowClosed:
00209             windowEvent.type = Event::WINDOW_CLOSE;
00210             break;
00211 
00212         case kEventWindowHidden:
00213         case kEventWindowCollapsed:
00214             windowEvent.type = Event::WINDOW_HIDE;
00215             break;
00216             
00217         case kEventWindowShown:
00218         case kEventWindowExpanded:
00219             windowEvent.type = Event::WINDOW_SHOW;
00220             if( carbonWindow == FrontNonFloatingWindow( ))
00221                 SetUserFocusWindow( carbonWindow );
00222             break;
00223 
00224         default:
00225             EQINFO << "Unhandled window event " << GetEventKind( event ) <<endl;
00226             windowEvent.type = Event::UNKNOWN;
00227             break;
00228     }
00229     windowEvent.originator = window->getID();
00230 
00231     EQLOG( LOG_EVENTS ) << "received event: " << windowEvent << endl;
00232     return _window->processEvent( windowEvent );
00233 }
00234 
00235 bool AGLEventHandler::_handleMouseEvent( EventRef event )
00236 {
00237     HIPoint        pos;
00238     AGLWindowEvent windowEvent;
00239 
00240     windowEvent.carbonEventRef = event;
00241     Window* const window       = _window->getWindow();
00242     
00243     const bool    decoration =
00244         window->getIAttribute( Window::IATTR_HINT_DECORATION ) != OFF;
00245     const int32_t menuHeight = decoration ? EQ_AGL_MENUBARHEIGHT : 0 ;
00246 
00247     switch( GetEventKind( event ))
00248     {
00249         case kEventMouseMoved:
00250         case kEventMouseDragged:
00251             windowEvent.type                  = Event::POINTER_MOTION;
00252             windowEvent.pointerMotion.button  = PTR_BUTTON_NONE;
00253             // Note: Luckily GetCurrentEventButtonState returns the same bits as
00254             // our button definitions.
00255             windowEvent.pointerMotion.buttons = _getButtonState();
00256 
00257             if( windowEvent.pointerMotion.buttons == PTR_BUTTON1 )
00258             {   // Only left button pressed: implement apple-style middle/right
00259                 // button if modifier keys are used.
00260                 uint32_t keys = 0;
00261                 GetEventParameter( event, kEventParamKeyModifiers, 
00262                                    typeUInt32, 0, sizeof( keys ), 0, &keys );
00263                 if( keys & controlKey )
00264                     windowEvent.pointerMotion.buttons = PTR_BUTTON3;
00265                 else if( keys & optionKey )
00266                     windowEvent.pointerMotion.buttons = PTR_BUTTON2;
00267             }
00268 
00269             GetEventParameter( event, kEventParamWindowMouseLocation, 
00270                                typeHIPoint, 0, sizeof( pos ), 0, 
00271                                &pos );
00272             if( pos.y < menuHeight )
00273                 return false; // ignore pointer events on the menu bar
00274 
00275             windowEvent.pointerMotion.x = static_cast< int32_t >( pos.x );
00276             windowEvent.pointerMotion.y = static_cast< int32_t >( pos.y ) -
00277                                                menuHeight;
00278 
00279             GetEventParameter( event, kEventParamMouseDelta, 
00280                                typeHIPoint, 0, sizeof( pos ), 0, 
00281                                &pos );
00282             windowEvent.pointerMotion.dx = static_cast< int32_t >( pos.x );
00283             windowEvent.pointerMotion.dy = static_cast< int32_t >( pos.y );
00284 
00285             _lastDX = windowEvent.pointerMotion.dx;
00286             _lastDY = windowEvent.pointerMotion.dy;
00287 
00288             _getRenderContext( window, windowEvent );
00289             break;
00290 
00291         case kEventMouseDown:
00292             windowEvent.type = Event::POINTER_BUTTON_PRESS;
00293             windowEvent.pointerMotion.buttons = _getButtonState();
00294             windowEvent.pointerButtonPress.button  =
00295                 _getButtonAction( event );
00296 
00297             if( windowEvent.pointerMotion.buttons == PTR_BUTTON1 )
00298             {   // Only left button pressed: implement apple-style middle/right
00299                 // button if modifier keys are used.
00300                 uint32_t keys = 0;
00301                 GetEventParameter( event, kEventParamKeyModifiers, 
00302                                    typeUInt32, 0, sizeof( keys ), 0, &keys );
00303                 if( keys & controlKey )
00304                     windowEvent.pointerMotion.buttons = PTR_BUTTON3;
00305                 else if( keys & optionKey )
00306                     windowEvent.pointerMotion.buttons = PTR_BUTTON2;
00307             }
00308 
00309             GetEventParameter( event, kEventParamWindowMouseLocation, 
00310                                typeHIPoint, 0, sizeof( pos ), 0, 
00311                                &pos );
00312             if( pos.y < menuHeight )
00313                 return false; // ignore pointer events on the menu bar
00314 
00315             windowEvent.pointerButtonPress.x = 
00316                 static_cast< int32_t >( pos.x );
00317             windowEvent.pointerButtonPress.y = 
00318                 static_cast< int32_t >( pos.y ) - menuHeight;
00319 
00320             windowEvent.pointerButtonPress.dx = _lastDX;
00321             windowEvent.pointerButtonPress.dy = _lastDY;
00322             _lastDX = 0;
00323             _lastDY = 0;
00324 
00325             _getRenderContext( window, windowEvent );
00326             break;
00327 
00328         case kEventMouseUp:
00329             windowEvent.type = Event::POINTER_BUTTON_RELEASE;
00330             windowEvent.pointerMotion.buttons = _getButtonState();
00331             windowEvent.pointerButtonRelease.button = 
00332                 _getButtonAction( event );
00333 
00334             if( windowEvent.pointerMotion.buttons == PTR_BUTTON1 )
00335             {   // Only left button pressed: implement apple-style middle/right
00336                 // button if modifier keys are used.
00337                 uint32_t keys = 0;
00338                 GetEventParameter( event, kEventParamKeyModifiers, 
00339                                    typeUInt32, 0, sizeof( keys ), 0, &keys );
00340                 if( keys & controlKey )
00341                     windowEvent.pointerMotion.buttons = PTR_BUTTON3;
00342                 else if( keys & optionKey )
00343                     windowEvent.pointerMotion.buttons = PTR_BUTTON2;
00344             }
00345 
00346             GetEventParameter( event, kEventParamWindowMouseLocation, 
00347                                typeHIPoint, 0, sizeof( pos ), 0, 
00348                                &pos );
00349             if( pos.y < menuHeight )
00350                 return false; // ignore pointer events on the menu bar
00351 
00352             windowEvent.pointerButtonRelease.x = 
00353                 static_cast< int32_t>( pos.x );
00354             windowEvent.pointerButtonRelease.y = 
00355                 static_cast< int32_t>( pos.y ) - menuHeight;
00356 
00357             windowEvent.pointerButtonRelease.dx = _lastDX;
00358             windowEvent.pointerButtonRelease.dy = _lastDY;
00359             _lastDX = 0;
00360             _lastDY = 0;
00361 
00362             _getRenderContext( window, windowEvent );
00363             break;
00364 
00365         default:
00366             EQINFO << "Unhandled mouse event " << GetEventKind( event ) << endl;
00367             windowEvent.type = Event::UNKNOWN;
00368             break;
00369     }
00370     windowEvent.originator = window->getID();
00371 
00372     EQLOG( LOG_EVENTS ) << "received event: " << windowEvent << endl;
00373     return _window->processEvent( windowEvent );
00374 }
00375 
00376 bool AGLEventHandler::_handleKeyEvent( EventRef event )
00377 {
00378     AGLWindowEvent windowEvent;
00379 
00380     windowEvent.carbonEventRef = event;
00381     Window* const window       = _window->getWindow();
00382 
00383     switch( GetEventKind( event ))
00384     {
00385         case kEventRawKeyDown:
00386         case kEventRawKeyRepeat:
00387             windowEvent.type         = Event::KEY_PRESS;
00388             windowEvent.keyPress.key = _getKey( event );
00389             break;
00390 
00391         case kEventRawKeyUp:
00392             windowEvent.type         = Event::KEY_RELEASE;
00393             windowEvent.keyPress.key = _getKey( event );
00394             break;
00395 
00396         default:
00397             EQINFO << "Unhandled keyboard event " << GetEventKind( event )
00398                    << endl;
00399             windowEvent.type = Event::UNKNOWN;
00400             break;
00401     }
00402     windowEvent.originator = window->getID();
00403 
00404     EQLOG( LOG_EVENTS ) << "received event: " << windowEvent << endl;
00405     return _window->processEvent( windowEvent );
00406 }
00407 
00408 uint32_t AGLEventHandler::_getButtonState()
00409 {
00410     const uint32 buttons = GetCurrentEventButtonState();
00411     
00412     // swap button 2&3
00413     return ( (buttons & 0xfffffff9u) +
00414              ((buttons & EQ_BIT3) >> 1) +
00415              ((buttons & EQ_BIT2) << 1) );
00416 }
00417 
00418 
00419 uint32_t AGLEventHandler::_getButtonAction( EventRef event )
00420 {
00421     EventMouseButton button;
00422     GetEventParameter( event, kEventParamMouseButton, 
00423                                typeMouseButton, 0, sizeof( button ), 0, 
00424                                &button );
00425 
00426     switch( button )
00427     {    
00428         case kEventMouseButtonPrimary:   return PTR_BUTTON1;
00429         case kEventMouseButtonSecondary: return PTR_BUTTON3;
00430         case kEventMouseButtonTertiary:  return PTR_BUTTON2;
00431         default: return PTR_BUTTON_NONE;
00432     }
00433 }
00434 
00435 uint32_t AGLEventHandler::_getKey( EventRef event )
00436 {
00437     unsigned char key;
00438     GetEventParameter( event, kEventParamKeyMacCharCodes, typeChar, 0,
00439                        sizeof( char ), 0, &key );
00440     switch( key )
00441     {
00442         case kEscapeCharCode:     return KC_ESCAPE;    
00443         case kBackspaceCharCode:  return KC_BACKSPACE; 
00444         case kReturnCharCode:     return KC_RETURN;    
00445         case kTabCharCode:        return KC_TAB;       
00446         case kHomeCharCode:       return KC_HOME;       
00447         case kLeftArrowCharCode:  return KC_LEFT;       
00448         case kUpArrowCharCode:    return KC_UP;         
00449         case kRightArrowCharCode: return KC_RIGHT;      
00450         case kDownArrowCharCode:  return KC_DOWN;       
00451         case kPageUpCharCode:     return KC_PAGE_UP;    
00452         case kPageDownCharCode:   return KC_PAGE_DOWN;  
00453         case kEndCharCode:        return KC_END;
00454         case kFunctionKeyCharCode:
00455         {
00456             uint32_t keyCode;
00457             GetEventParameter( event, kEventParamKeyCode, typeUInt32, 0,
00458                        sizeof( keyCode ), 0, &keyCode );
00459 
00460             switch( keyCode )
00461             {
00462                 case kVK_F1:        return KC_F1;         
00463                 case kVK_F2:        return KC_F2;         
00464                 case kVK_F3:        return KC_F3;         
00465                 case kVK_F4:        return KC_F4;         
00466                 case kVK_F5:        return KC_F5;         
00467                 case kVK_F6:        return KC_F6;         
00468                 case kVK_F7:        return KC_F7;         
00469                 case kVK_F8:        return KC_F8;         
00470                 case kVK_F9:        return KC_F9;         
00471                 case kVK_F10:       return KC_F10;        
00472                 case kVK_F11:       return KC_F11;        
00473                 case kVK_F12:       return KC_F12;        
00474                 case kVK_F13:       return KC_F13;        
00475                 case kVK_F14:       return KC_F14;        
00476                 case kVK_F15:       return KC_F15;        
00477                 case kVK_F16:       return KC_F16;        
00478                 case kVK_F17:       return KC_F17;        
00479                 case kVK_F18:       return KC_F18;        
00480                 case kVK_F19:       return KC_F19;        
00481                 case kVK_F20:       return KC_F20;        
00482 #if 0
00483                 case XK_Shift_L:   return KC_SHIFT_L;    
00484                 case XK_Shift_R:   return KC_SHIFT_R;    
00485                 case XK_Control_L: return KC_CONTROL_L;  
00486                 case XK_Control_R: return KC_CONTROL_R;  
00487                 case XK_Alt_L:     return KC_ALT_L;      
00488                 case XK_Alt_R:     return KC_ALT_R;
00489 #endif
00490             }
00491         }
00492             
00493         default: 
00494             // 'Useful' Latin1 characters
00495             if(( key >= ' ' && key <= '~' ) ||
00496                ( key >= 0xa0 /*XK_nobreakspace && key <= XK_ydiaeresis*/ ))
00497 
00498                 return key;
00499 
00500             EQWARN << "Unrecognized key " << key << endl;
00501             return KC_VOID;
00502     }
00503 }
00504 
00505 
00506 #ifdef EQ_USE_MAGELLAN
00507 extern "C" OSErr InstallConnexionHandlers( ConnexionMessageHandlerProc, 
00508                                            ConnexionAddedHandlerProc, 
00509                                            ConnexionRemovedHandlerProc ) 
00510     __attribute__((weak_import));
00511 
00512 namespace
00513 {
00514 static uint16_t _magellanID = 0;
00515 static Node*    _magellanNode = 0;
00516 
00517 void _magellanEventHandler( io_connect_t connection, natural_t messageType, 
00518                             void *messageArgument ) 
00519 { 
00520     switch (messageType) 
00521     { 
00522         case kConnexionMsgDeviceState: 
00523         {
00524             ConnexionDeviceState *state = static_cast< ConnexionDeviceState* >(
00525                                                               messageArgument );
00526             if( state->client == _magellanID ) 
00527             { 
00528                 ConfigEvent event;
00529                 event.data.originator = _magellanNode->getID();
00530                 event.data.magellan.buttons = state->buttons;
00531                 event.data.magellan.xAxis = state->axis[0];
00532                 event.data.magellan.yAxis = state->axis[1];
00533                 event.data.magellan.zAxis = state->axis[2];
00534                 event.data.magellan.xRotation = state->axis[3];
00535                 event.data.magellan.yRotation = state->axis[4];
00536                 event.data.magellan.zRotation = state->axis[5];
00537 
00538                 // decipher what command/event is being reported by the driver 
00539                 switch( state->command ) 
00540                 { 
00541                     case kConnexionCmdHandleAxis: 
00542                         event.data.type = Event::MAGELLAN_AXIS;
00543                         event.data.magellan.button = 0;
00544                         break; 
00545 
00546                     case kConnexionCmdHandleButtons:
00547                         event.data.type = Event::MAGELLAN_BUTTON;
00548                         event.data.magellan.button = state->value;
00549                         break; 
00550                         
00551                     default:
00552                         EQASSERTINFO( 0, "Unimplemented space mouse command " <<
00553                                       state->command );
00554                 }                 
00555                         
00556                 _magellanNode->getConfig()->sendEvent( event );
00557             } 
00558             break; 
00559         }
00560         default: 
00561             // other messageTypes can happen and should be ignored 
00562             break; 
00563     } 
00564 }
00565 
00566 }
00567 #endif
00568 
00569 void AGLEventHandler::initMagellan( Node* node )
00570 {
00571 #ifdef EQ_USE_MAGELLAN
00572     if( _magellanNode )
00573         EQINFO << "Space Mouse already installed" << std::endl;
00574     else if( !InstallConnexionHandlers )
00575         EQWARN << "Space Mouse drivers not installed" << std::endl;
00576     else if( InstallConnexionHandlers( _magellanEventHandler, 0, 0 ) != noErr )
00577         EQWARN << "Can't install Space Mouse connexion handlers" << std::endl;
00578     else
00579     {
00580         std::string program( '\0' + 
00581                             base::getFilename( net::Global::getProgramName( )));
00582         program[0] = program.length() - 1;
00583 
00584         _magellanID = RegisterConnexionClient( 0, (uint8_t*)program.c_str( ),
00585                                                kConnexionClientModeTakeOver,
00586                                                kConnexionMaskAll );
00587         _magellanNode = node;
00588     }
00589 #endif
00590 }
00591 
00592 void AGLEventHandler::exitMagellan( Node* node )
00593 {
00594 #ifdef EQ_USE_MAGELLAN
00595     if( _magellanID && _magellanNode == node )
00596     {
00597         UnregisterConnexionClient( _magellanID );
00598         CleanupConnexionHandlers();
00599         _magellanID = 0;
00600         _magellanNode = 0;
00601     }
00602 #endif
00603 }
00604 
00605 }
Generated on Mon Aug 10 18:58:31 2009 for Equalizer 0.9 by  doxygen 1.5.8