]> Shamusworld >> Repos - apple2/blobdiff - src/gui/gui.cpp
Undoing changes (accidentally) committed from r31.
[apple2] / src / gui / gui.cpp
index ef5ef97b507417337094fcdffee16fc375e3c262..2841992fc2ba07e8ddf2ab7cff55d1ee9f996067 100755 (executable)
 // ---  ----------  ------------------------------------------------------------
 // JLH  02/03/2006  Created this file
 // JLH  03/13/2006  Added functions to allow shutting down GUI externally
+// JLH  03/22/2006  Finalized basic multiple window support
 //
-
-// STILL TO FIX:
+// STILL TO DO:
 //
-// - Memory leak on quitting with a window active
-// - Multiple window handling
+// - Memory leak on quitting with a window active [DONE]
+// - Multiple window handling [DONE]
 //
 
 #include "gui.h"
 
 //#define DEBUG_MAIN_LOOP
 
-#ifdef DEBUG_MAIN_LOOP
+//#ifdef DEBUG_MAIN_LOOP
 #include "log.h"
-#endif
+//#endif
 
 
-GUI::GUI(SDL_Surface * mainSurface): mainMenu(new Menu()), menuItem(new MenuItems())
+GUI::GUI(SDL_Surface * mainSurface): menuItem(new MenuItems())
 {
+       windowList.push_back(new Menu());
        Element::SetScreen(mainSurface);
 }
 
 GUI::~GUI()
 {
-       if (mainMenu)
-               delete mainMenu;
+       // Clean up menuItem, if any
 
        if (menuItem)
                delete menuItem;
+
+       // Clean up the rest
+
+       for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
+               if (*i)
+                       delete *i;
 }
 
 void GUI::AddMenuTitle(const char * title)
@@ -59,25 +65,32 @@ void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDLKey
 
 void GUI::CommitItemsToMenu(void)
 {
-       mainMenu->Add(*menuItem);
-}
+//We could just do a simple check here to see if more than one item is in the list,
+//and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
+
+//Right now, we just silently fail...
+       if (windowList.size() > 1)
+       {
+               WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
+               return;
+       }
 
+       ((Menu *)(*windowList.begin()))->Add(*menuItem);
+}
 
 void GUI::Run(void)
 {
        exitGUI = false;
-
-       bool showMouse = true;
-       int mouseX = 0, mouseY = 0;
-       int oldMouseX = 0, oldMouseY = 0;
-       Element * mainWindow = NULL;
+       showMouse = true;
        SDL_Event event;
+       std::list<Element *>::iterator i;
 
        SDL_EnableKeyRepeat(150, 75);
-       // Initial update...
-//Shouldn't we save the state of the GUI instead of doing things this way?
-//We have a memory leak whenever a mainWindow is active and we quit... !!! FIX !!!
-       mainMenu->Draw();
+
+       // Initial update... [Now handled correctly in the constructor]
+       for(i=windowList.begin(); i!=windowList.end(); i++)
+               (*i)->Draw();
+
        RenderScreenBuffer();
 
        // Main loop
@@ -96,35 +109,47 @@ WriteLog(" -- SDL_USEREVENT\n");
 //Mebbe add another user event for screen refresh? Why not!
                                if (event.user.code == WINDOW_CLOSE)
                                {
-                                       delete mainWindow;
-                                       mainWindow = NULL;
+                                       for(i=windowList.begin(); i!=windowList.end(); i++)
+                                       {
+                                               if (*i == (Element *)event.user.data1)
+                                               {
+                                                       delete *i;
+                                                       windowList.erase(i);
+                                                       break;
+                                               }
+                                       }
                                }
                                else if (event.user.code == MENU_ITEM_CHOSEN)
                                {
                                        // Confused? Let me enlighten... What we're doing here is casting
-                                       // data1 as a pointer to a function which returns a Window pointer and
-                                       // which takes no parameters (the "(Window *(*)(void))" part), then
+                                       // data1 as a pointer to a function which returns a Element pointer and
+                                       // which takes no parameters (the "(Element *(*)(void))" part), then
                                        // derefencing it (the "*" in front of that) in order to call the
                                        // function that it points to. Clear as mud? Yeah, I hate function
                                        // pointers too, but what else are you gonna do?
-                                       mainWindow = (*(Element *(*)(void))event.user.data1)();
+                                       Element * window = (*(Element *(*)(void))event.user.data1)();
+
+                                       if (window)
+                                               windowList.push_back(window);
 
                                        while (SDL_PollEvent(&event));  // Flush the event queue...
+
                                        event.type = SDL_MOUSEMOTION;
                                        int mx, my;
                                        SDL_GetMouseState(&mx, &my);
                                        event.motion.x = mx, event.motion.y = my;
                                    SDL_PushEvent(&event);                      // & update mouse position...!
 
-                                       oldMouseX = mouseX, oldMouseY = mouseY;
-                                       mouseX = mx, mouseY = my;               // This prevents "mouse flash"...
+                                       oldMouse.x = mouse.x, oldMouse.y = mouse.y;
+                                       mouse.x = mx, mouse.y = my;             // This prevents "mouse flash"...
                                }
-//There's a *small* problem with this approach--if a window and a bunch of child
-//widgets send this message, we'll get a bunch of unnecessary refresh events...
+//There's a *small* problem with the following approach--if a window and a bunch of
+//child widgets send this message, we'll get a bunch of unnecessary refresh events...
 //This could be controlled by having the main window refresh itself intelligently...
 
 //What we could do instead is set a variable in Element and check it after the fact
 //to see whether or not a refresh is needed.
+//[This is what we do now.]
 
 //Dirty rectangle is also possible...
                                else if (event.user.code == SCREEN_REFRESH_NEEDED)
@@ -132,58 +157,235 @@ WriteLog(" -- SDL_USEREVENT\n");
                        }
                        else if (event.type == SDL_ACTIVEEVENT)
                        {
+//Need to do a screen refresh here...
                                if (event.active.state == SDL_APPMOUSEFOCUS)
                                        showMouse = (event.active.gain ? true : false);
+
+                               RenderScreenBuffer();
                        }
                        else if (event.type == SDL_KEYDOWN)
                        {
 #ifdef DEBUG_MAIN_LOOP
 WriteLog(" -- SDL_KEYDOWN\n");
 #endif
-                               if (event.key.keysym.sym == SDLK_F5)
+                               if (event.key.keysym.sym == SDLK_F1)
                                        exitGUI = true;
 
-                               if (mainWindow)
-                                       mainWindow->HandleKey(event.key.keysym.sym);
-                               else
-                                       mainMenu->HandleKey(event.key.keysym.sym);
+//Not sure that this is the right way to handle this...
+//Probably should only give this to the top level window...
+//                             for(i=windowList.begin(); i!=windowList.end(); i++)
+//                                     (*i)->HandleKey(event.key.keysym.sym);
+                               windowList.back()->HandleKey(event.key.keysym.sym);
                        }
                        else if (event.type == SDL_MOUSEMOTION)
                        {
 #ifdef DEBUG_MAIN_LOOP
 WriteLog(" -- SDL_MOUSEMOTION\n");
 #endif
-                               oldMouseX = mouseX, oldMouseY = mouseY;
-                               mouseX = event.motion.x, mouseY = event.motion.y;
+//This is for tracking a custom mouse cursor, which we're not doing--YET.
+                               oldMouse.x = mouse.x, oldMouse.y = mouse.y;
+                               mouse.x = event.motion.x, mouse.y = event.motion.y;
 
-                               if (mainWindow)
-                                       mainWindow->HandleMouseMove(mouseX, mouseY);
-                               else
-                                       mainMenu->HandleMouseMove(mouseX, mouseY);
+//Not sure that this is the right way to handle this...
+//Right now, we should probably only do mouseover for the last item in the list...
+//And now we do!
+//Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
+//Or maybe to just the ones that aren't completely obscured?
+//Probably. Right now, a disk's close button that should be obscured by one sitting on
+//top of it gets redrawn. Not good.
+                               for(i=windowList.begin(); i!=windowList.end(); i++)
+                                       (*i)->HandleMouseMove(mouse.x, mouse.y);
+//                             windowList.back()->HandleMouseMove(mouse.x, mouse.y);
                        }
                        else if (event.type == SDL_MOUSEBUTTONDOWN)
                        {
 #ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_MOSEBUTTONDOWN\n");
+WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
 #endif
-                               uint32 mx = event.button.x, my = event.button.y;
+//Not sure that this is the right way to handle this...
+// What we should do here is ensure that whatever has been clicked on gets moved to the
+// highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
+//[DONE]
+
+/*
+
+We could do the following:
+
+- Go through list and find which window has been clicked on (if any). If more
+  than one is clicked on, take the one highest in the Z order (closer to the end
+  of the list).
+
+- If item is highest in Z order, pack click through to window and exit.
+
+- Otherwise, restore backing store on each window in reverse order.
+
+- Remove item clicked on from the list. Put removed item at the end of the list.
+
+- Go through list and pass click through to each window in the list. Also do a
+  blit to backing store and a Draw() for each window.
+
+Could also do a check (if not clicked on highest Z window) to see which windows
+it overlaps and just do restore/redraw for those that overlap. To wit:
+
+- Create new list containing only those windows that overlap the clicking on window.
+
+- Go through list and do a blit to backing store and a Draw() for each window.
+
+- Go through list and pass click through to each window in the list.
+
+*/
+
+#if 0
+#if 0
+                               for(i=windowList.begin(); i!=windowList.end(); i++)
+                                       (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+#else
+// We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
+// to the 2nd...
 
-                               if (mainWindow)
-                                       mainWindow->HandleMouseButton(mx, my, true);
+                               // Walk backward through the list and see if a window was hit.
+                               // This will automagically return us the window with the highest Z.
+
+                               std::list<Element *>::reverse_iterator ri;
+                               std::list<Element *>::iterator hit;// = windowList.end();
+
+                               for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
+                               {
+                                       if ((*ri)->Inside(event.button.x, event.button.y))
+                                       {
+                                               // Here's a bit of STL weirdness: Converting from a reverse
+                                               // iterator to a regular iterator requires backing the iterator
+                                               // up a position after grabbing it's base() OR going forward
+                                               // one position with the reverse iterator before grabbing base().
+                                               // Ugly, but it get the job done...
+                                               hit = (++ri).base();
+                                               // Put it back where we found it, so the tests following this
+                                               // don't fail...
+                                               ri--;
+                                               break;
+                                       }
+                               }
+
+                               // If we hit the highest in the list, then pass the event through
+                               // to the window for handling. if we hit no windows, then pass the
+                               // event to all windows. Otherwise, we need to shuffle windows.
+
+//NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
+                               if (ri == windowList.rbegin())
+                               {
+                                       for(i=windowList.begin(); i!=windowList.end(); i++)
+                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+                               }
+                               else if (ri == windowList.rend())
+                               {
+                                       for(i=windowList.begin(); i!=windowList.end(); i++)
+                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+                               }
                                else
-                                       mainMenu->HandleMouseButton(mx, my, true);
+                               {
+// - Otherwise, restore backing store on each window in reverse order.
+                                       for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
+                                               (*ri)->RestoreScreenFromBackstore();
+                                       // At this point, the screen has been restored...
+
+// - Remove item clicked on from the list. Put removed item at the end of the list.
+                                       windowList.push_back(*hit);
+                                       windowList.erase(hit);
+// - Go through list and pass click through to each window in the list. Also do a
+//  blit to backing store and a Draw() for each window.
+                                       for(i=windowList.begin(); i!= windowList.end(); i++)
+                                       {
+                                               // Grab bg into backstore
+                                               (*i)->SaveScreenToBackstore();
+                                               // Pass click
+                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+                                               // Draw?
+                                               (*i)->Draw();
+                                       }
+                               }
+#endif
+#endif
+/*
+A slightly different way to handle this would be to loop through all windows, compare
+all those above it to see if they obscure it; if so then subdivide it's update rectangle
+to eliminate drawing the parts that aren't shown. The beauty of this approach is that
+you don't have to care what order the windows are drawn in and you don't need to worry
+about the order of restoring the backing store.
+
+You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
+correct, but that's not too terrible.
+
+Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
+*/
+                               std::list<Element *>::reverse_iterator ri;
+                               bool movedWindow = false;
+
+                               for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
+                               {
+                                       if ((*ri)->Inside(event.button.x, event.button.y))
+                                       {
+                                               // Remove item clicked on from the list & put removed item at the
+                                               // end of the list, thus putting the window at the top of the Z
+                                               // order. But IFF window is not already topmost!
+                                               if (ri != windowList.rbegin())
+                                               {
+                                                       windowList.push_back(*ri);
+                                                       // Here's a bit of STL weirdness: Converting from a reverse
+                                                       // iterator to a regular iterator requires backing the iterator
+                                                       // up a position after grabbing it's base() OR going forward
+                                                       // one position with the reverse iterator before grabbing base().
+                                                       // Ugly, but it get the job done...
+                                                       windowList.erase((++ri).base());
+                                                       movedWindow = true;
+                                               }
+
+                                               break;
+                                       }
+                               }
+
+//Small problem here: we should only pass the *hit* to the topmost window and pass
+//*misses* to everyone else... Otherwise, you can have overlapping draggable windows
+//and be able to drag both by clicking on a point that intersects both...
+//(though that may be an interesting way to handle things!)
+                               // Pass the click on to all windows
+                               for(i=windowList.begin(); i!=windowList.end(); i++)
+                                       (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+
+//                             // & bail if nothing changed...
+                               if (movedWindow)
+//                                     return;
+{
+                               // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
+//One way to optimize this would be to only reset coverage lists from the point in
+//the Z order where the previous window was.
+                               for(i=windowList.begin(); i!=windowList.end(); i++)
+                               {
+                                       (*i)->ResetCoverageList();
+
+                                       // This looks odd, but it's just a consequence of iterator weirdness.
+                                       // Otherwise we could just stick a j+1 in the for loop below. :-P
+                                       std::list<Element *>::iterator j = i;
+                                       j++;
+
+                                       for(; j!=windowList.end(); j++)
+                                               (*i)->AdjustCoverageList((*j)->GetExtents());
+
+//                                     (*i)->HandleMouseButton(event.button.x, event.button.y, true);
+                                       (*i)->Draw();
+                               }
+}
                        }
                        else if (event.type == SDL_MOUSEBUTTONUP)
                        {
 #ifdef DEBUG_MAIN_LOOP
 WriteLog(" -- SDL_MOUSEBUTTONUP\n");
 #endif
-                               uint32 mx = event.button.x, my = event.button.y;
-
-                               if (mainWindow)
-                                       mainWindow->HandleMouseButton(mx, my, false);
-                               else
-                                       mainMenu->HandleMouseButton(mx, my, false);
+//Not sure that this is the right way to handle this...
+                               for(i=windowList.begin(); i!=windowList.end(); i++)
+                                       (*i)->HandleMouseButton(event.button.x, event.button.y, false);
+//I think we should only do topmost here...
+//Or should we???
+//                             windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
                        }
 #ifdef DEBUG_MAIN_LOOP
 else