Initial work on callbacks with user data parameters.
[freeglut] / src / fg_menu.c
index 8af9697..cad5b83 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * freeglut_menu.c
+ * fg_menu.c
  *
  * Pull-down menu creation and handling.
  *
@@ -65,7 +65,7 @@
  * These variables are for rendering the freeglut menu items.
  *
  * The choices are fore- and background, with and without h for Highlighting.
- * Old GLUT appeared to be system-dependant for its colors (sigh) so we are
+ * Old GLUT appeared to be system-dependent for its colors (sigh) so we are
  * too.  These variables should be stuffed into global state and initialized
  * via the glutInit*() system.
  */
@@ -78,6 +78,7 @@ static float menu_pen_hback [4] = FREEGLUT_MENU_PEN_HBACK_COLORS;
 extern GLvoid fgPlatformGetGameModeVMaxExtent( SFG_Window* window, int* x, int* y );
 extern void fghPlatformGetCursorPos(const SFG_Window *window, GLboolean client, SFG_XYUse *mouse_pos);
 extern SFG_Font* fghFontByID( void* font );
+extern void fgPlatformHideWindow( SFG_Window* window );
 
 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
 
@@ -108,8 +109,7 @@ static void fghDeactivateSubMenu( SFG_MenuEntry *menuEntry )
 {
     SFG_MenuEntry *subMenuIter;
     /* Hide the present menu's window */
-    fgSetWindow( menuEntry->SubMenu->Window );
-    glutHideWindow( );
+    fgPlatformHideWindow( menuEntry->SubMenu->Window );
 
     /* Forget about having that menu active anymore, now: */
     menuEntry->SubMenu->Window->ActiveMenu = NULL;
@@ -127,8 +127,6 @@ static void fghDeactivateSubMenu( SFG_MenuEntry *menuEntry )
         if( subMenuIter->SubMenu )
             fghDeactivateSubMenu( subMenuIter );
     }
-
-    fgSetWindow ( menuEntry->SubMenu->ParentWindow ) ;
 }
 
 /*
@@ -209,16 +207,16 @@ static GLboolean fghCheckMenuStatus( SFG_Menu* menu )
 
         if( menuEntry != menu->ActiveEntry )
         {
-            menu->Window->State.Redisplay = GL_TRUE;
+            menu->Window->State.WorkMask |= GLUT_DISPLAY_WORK;
             if( menu->ActiveEntry )
                 menu->ActiveEntry->IsActive = GL_FALSE;
         }
 
         menu->ActiveEntry = menuEntry;
-        menu->IsActive = GL_TRUE;  /* XXX Do we need this? */
+        menu->IsActive = GL_TRUE;
 
         /*
-         * OKi, we have marked that entry as active, but it would be also
+         * OK, we have marked that entry as active, but it would be also
          * nice to have its contents updated, in case it's a sub menu.
          * Also, ignore the return value of the check function:
          */
@@ -267,7 +265,7 @@ static GLboolean fghCheckMenuStatus( SFG_Menu* menu )
             }
 
             /* Activate it because its parent entry is active */
-            menuEntry->SubMenu->IsActive = GL_TRUE;  /* XXX Do we need this? */
+            menuEntry->SubMenu->IsActive = GL_TRUE;
         }
 
         /* Report back that we have caught the menu cursor */
@@ -279,7 +277,7 @@ static GLboolean fghCheckMenuStatus( SFG_Menu* menu )
         ( !menu->ActiveEntry->SubMenu ||
           !menu->ActiveEntry->SubMenu->IsActive ) )
     {
-        menu->Window->State.Redisplay = GL_TRUE;
+        menu->Window->State.WorkMask |= GLUT_DISPLAY_WORK;
         menu->ActiveEntry->IsActive = GL_FALSE;
         menu->ActiveEntry = NULL;
     }
@@ -421,36 +419,6 @@ static void fghSetMenuParentWindow( SFG_Window *window, SFG_Menu *menu )
             fghSetMenuParentWindow( window, menuEntry->SubMenu );
 }
 
-/*
- * Function to check for menu entry selection on menu deactivation
- */
-static void fghExecuteMenuCallback( SFG_Menu* menu )
-{
-    SFG_MenuEntry *menuEntry;
-
-    /* First of all check any of the active sub menus... */
-    for( menuEntry = (SFG_MenuEntry *)menu->Entries.First;
-         menuEntry;
-         menuEntry = (SFG_MenuEntry *)menuEntry->Node.Next)
-    {
-        if( menuEntry->IsActive )
-        {
-            if( menuEntry->SubMenu )
-                fghExecuteMenuCallback( menuEntry->SubMenu );
-            else
-                if( menu->Callback )
-                {
-                    SFG_Menu *save_menu = fgStructure.CurrentMenu;
-                    fgStructure.CurrentMenu = menu;
-                    menu->Callback( menuEntry->ID );
-                    fgStructure.CurrentMenu = save_menu;
-                }
-
-            return;
-        }
-    }
-}
-
 
 /*
  * Displays the currently active menu for the current window
@@ -578,8 +546,7 @@ static void fghActivateMenu( SFG_Window* window, int button )
 
 /*
  * Update Highlight states of the menu
- *
- * Current mouse position is in menu->Window->State.MouseX/Y.
+ * NB: Current mouse position is in menu->Window->State.MouseX/Y
  */
 void fgUpdateMenuHighlight ( SFG_Menu *menu )
 {
@@ -592,13 +559,17 @@ void fgUpdateMenuHighlight ( SFG_Menu *menu )
 GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
                               int mouse_x, int mouse_y )
 {
+    GLboolean is_handled = GL_FALSE;
+    GLboolean is_clicked = GL_FALSE;
     /*
      * Near as I can tell, this is the menu behaviour:
      *  - Down-click the menu button, menu not active:  activate
      *    the menu with its upper left-hand corner at the mouse
      *    location.
      *  - Down-click any button outside the menu, menu active:
-     *    deactivate the menu
+     *    deactivate the menu, and potentially activate a new menu
+     *    at the new mouse location. This includes clicks in
+     *    different windows of course
      *  - Down-click any button inside the menu, menu active:
      *    select the menu entry and deactivate the menu
      *  - Up-click the menu button, menu not active:  nothing happens
@@ -618,7 +589,7 @@ GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
                                        mouse_y - window->ActiveMenu->Y;
         }
 
-        /* In the menu, invoke the callback and deactivate the menu */
+        /* In the menu, deactivate the menu and invoke the callback */
         if( fghCheckMenuStatus( window->ActiveMenu ) )
         {
             /*
@@ -626,18 +597,26 @@ GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
              * window to the window whose menu this is
              */
             SFG_Window *save_window = fgStructure.CurrentWindow;
-            SFG_Menu *save_menu = fgStructure.CurrentMenu;
+            SFG_Menu *save_menu = fgStructure.CurrentMenu, *active_menu = window->ActiveMenu;   /* active menu is always the one with the mouse in it, due to fghCheckMenuStatus */
+            SFG_MenuEntry *active_entry = active_menu->ActiveEntry;                             /* currently highlighted item -> must be the one that was just clicked */
             SFG_Window *parent_window = window->ActiveMenu->ParentWindow;
-            fgSetWindow( parent_window );
-            fgStructure.CurrentMenu = window->ActiveMenu;
 
-            /* Execute the menu callback */
-            fghExecuteMenuCallback( window->ActiveMenu );
-            fgDeactivateMenu( parent_window );
+            /* ignore clicks on the submenu entry */
+            if (!active_entry->SubMenu)
+            {
+                fgSetWindow( parent_window );
+                fgStructure.CurrentMenu = active_menu;
+
+                /* Deactivate menu and then call callback (we don't want menu to stay in view while callback is executing, and user should be able to change menus in callback) */
+                fgDeactivateMenu( parent_window );
+                active_menu->Callback( active_entry->ID, active_menu->CallbackData );
 
-            /* Restore the current window and menu */
-            fgSetWindow( save_window );
-            fgStructure.CurrentMenu = save_menu;
+                /* Restore the current window and menu */
+                fgSetWindow( save_window );
+                fgStructure.CurrentMenu = save_menu;
+            }
+
+            is_clicked = GL_TRUE;   /* Don't reopen... */
         }
         else if( pressed )
             /*
@@ -648,22 +627,24 @@ GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
              */
         {
             fgDeactivateMenu( window->ActiveMenu->ParentWindow );
+            /* Could reopen again in different location, as is_clicked remains false */
         }
 
-        /*
-         * XXX Why does an active menu require a redisplay at
-         * XXX this point?  If this can come out cleanly, then
-         * XXX it probably should do so; if not, a comment should
-         * XXX explain it.
-         */
-        if( ! window->IsMenu )
-            window->State.Redisplay = GL_TRUE;
+        is_handled = GL_TRUE;
+    }
+    else if ( fgState.ActiveMenus ) /* Don't have to check whether this was a downpress or an uppress, there is no way to get an uppress in another window before a downpress... */
+    {
+        /* if another window than the one clicked in has an open menu, close it */
+        SFG_Menu *menu = fgGetActiveMenu();
+        if ( menu ) /* any open menu? */
+            fgDeactivateMenu( menu->ParentWindow );
 
-        return GL_TRUE;
+        /* Leave is_handled to false, we didn't do anything relevant from the perspective of the window that was clicked */
     }
 
     /* No active menu, let's check whether we need to activate one. */
-    if( ( 0 <= button ) &&
+    if( !is_clicked &&
+        ( 0 <= button ) &&
         ( FREEGLUT_MAX_MENUS > button ) &&
         ( window->Menu[ button ] ) &&
         pressed )
@@ -675,14 +656,12 @@ GLboolean fgCheckActiveMenu ( SFG_Window *window, int button, GLboolean pressed,
         if (window->State.MouseX>0 && window->State.MouseY>0 &&
             window->State.MouseX<window->State.Width && window->State.MouseY<window->State.Height)
         {
-            /* XXX Posting a requisite Redisplay seems bogus. */
-            window->State.Redisplay = GL_TRUE;
             fghActivateMenu( window, button );
-            return GL_TRUE;
+            is_handled = GL_TRUE;
         }
     }
 
-    return GL_FALSE;
+    return is_handled;
 }
 
 /*
@@ -708,8 +687,7 @@ void fgDeactivateMenu( SFG_Window *window )
     parent_window = menu->ParentWindow;
 
     /* Hide the present menu's window */
-    fgSetWindow( menu->Window );
-    glutHideWindow( );
+    fgPlatformHideWindow( menu->Window );
 
     /* Forget about having that menu active anymore, now: */
     menu->Window->ActiveMenu = NULL;
@@ -734,8 +712,6 @@ void fgDeactivateMenu( SFG_Window *window )
     /* Done deactivating menu */
     menuDeactivating = NULL;
 
-    fgSetWindow ( parent_window ) ;
-
     /* Menu status callback */
     if (fgState.MenuStateCallback || fgState.MenuStatusCallback)
     {
@@ -778,9 +754,9 @@ void fghCalculateMenuBoxSize( void )
 
         /*
          * If the entry is a submenu, then it needs to be wider to
-         * accomodate the arrow. JCJ 31 July 2003
+         * accommodate the arrow.
          */
-        if (menuEntry->SubMenu )
+        if (menuEntry->SubMenu)
             menuEntry->Width += glutBitmapLength(
                 fgStructure.CurrentMenu->Font,
                 (unsigned char *)"_"
@@ -804,14 +780,27 @@ void fghCalculateMenuBoxSize( void )
 /*
  * Creates a new menu object, adding it to the freeglut structure
  */
-int FGAPIENTRY glutCreateMenu( FGCBMenu callback )
+int FGAPIENTRY glutCreateMenuUcall( FGCBMenuUC callback, FGCBUserData userData )
 {
-    /* The menu object creation code resides in freeglut_structure.c */
-    FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutCreateMenu" );
-    if (fgGetActiveMenu())
+    /* The menu object creation code resides in fg_structure.c */
+    FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutCreateMenuUcall" );
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
-    return fgCreateMenu( callback )->ID;
+    return fgCreateMenu( callback, userData )->ID;
+}
+
+/* Standard glutCreateMenu */
+void glutCreateMenuCallback( int menu, FGCBUserData userData )
+{
+    FGCBMenu callback = (FGCBMenu)userData;
+    callback( menu );
+}
+
+int FGAPIENTRY glutCreateMenu( FGCBMenu callback )
+{
+    FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutCreateMenu" );
+    return glutCreateMenuUcall( glutCreateMenuCallback, (FGCBUserData)callback );
 }
 
 /*
@@ -825,10 +814,10 @@ void FGAPIENTRY glutDestroyMenu( int menuID )
     menu = fgMenuByID( menuID );
 
     freeglut_return_if_fail( menu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
-    /* The menu object destruction code resides in freeglut_structure.c */
+    /* The menu object destruction code resides in fg_structure.c */
     fgDestroyMenu( menu );
 }
 
@@ -870,7 +859,7 @@ void FGAPIENTRY glutAddMenuEntry( const char* label, int value )
     menuEntry = (SFG_MenuEntry *)calloc( sizeof(SFG_MenuEntry), 1 );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     menuEntry->Text = strdup( label );
@@ -895,7 +884,7 @@ void FGAPIENTRY glutAddSubMenu( const char *label, int subMenuID )
     subMenu = fgMenuByID( subMenuID );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     freeglut_return_if_fail( subMenu );
@@ -911,19 +900,23 @@ void FGAPIENTRY glutAddSubMenu( const char *label, int subMenuID )
 /*
  * Changes the current menu's font
  */
-void FGAPIENTRY glutSetMenuFont( void* fontID )
+void FGAPIENTRY glutSetMenuFont( int menuID, void* fontID )
 {
     SFG_Font* font;
+    SFG_Menu* menu;
     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutSetMenuFont" );
-    freeglut_return_if_fail( fgStructure.CurrentMenu );
+    menu = fgMenuByID( menuID );
+    freeglut_return_if_fail( menu );
 
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     font = fghFontByID( fontID );
     if (!font)
+    {
         fgWarning("glutChangeMenuFont: bitmap font 0x%08x not found. Make sure you're not passing a stroke font. Ignoring...\n",fontID);
-    freeglut_return_if_fail( font );
+        return;
+    }
 
     fgStructure.CurrentMenu->Font = fontID;
     fghCalculateMenuBoxSize( );
@@ -939,7 +932,7 @@ void FGAPIENTRY glutChangeToMenuEntry( int item, const char* label, int value )
     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToMenuEntry" );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     /* Get n-th menu entry in the current menu, starting from one: */
@@ -969,7 +962,7 @@ void FGAPIENTRY glutChangeToSubMenu( int item, const char* label,
     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutChangeToSubMenu" );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     /* Get handle to sub menu */
@@ -1002,7 +995,7 @@ void FGAPIENTRY glutRemoveMenuItem( int item )
     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutRemoveMenuItem" );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     /* Get n-th menu entry in the current menu, starting from one: */
@@ -1028,7 +1021,7 @@ void FGAPIENTRY glutAttachMenu( int button )
     freeglut_return_if_fail( fgStructure.CurrentWindow );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     freeglut_return_if_fail( button >= 0 );
@@ -1047,7 +1040,7 @@ void FGAPIENTRY glutDetachMenu( int button )
     freeglut_return_if_fail( fgStructure.CurrentWindow );
 
     freeglut_return_if_fail( fgStructure.CurrentMenu );
-    if (fgGetActiveMenu())
+    if (fgState.ActiveMenus)
         fgError("Menu manipulation not allowed while menus in use.");
 
     freeglut_return_if_fail( button >= 0 );