]> Shamusworld >> Repos - apple2/blob - src/gui/gui.cpp
Moved stuff into trunk (part 2)...
[apple2] / src / gui / gui.cpp
1 //
2 // GUI.CPP
3 //
4 // Graphical User Interface support
5 // by James L. Hammons
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  02/03/2006  Created this file
12 // JLH  03/13/2006  Added functions to allow shutting down GUI externally
13 // JLH  03/22/2006  Finalized basic multiple window support
14 //
15 // STILL TO DO:
16 //
17 // - Memory leak on quitting with a window active [DONE]
18 // - Multiple window handling [DONE]
19 //
20
21 #include "gui.h"
22 #include "menu.h"                                                               // Element class methods are pulled in here...
23 #include "window.h"
24 #include "video.h"
25
26 // Debug support
27
28 //#define DEBUG_MAIN_LOOP
29
30 //#ifdef DEBUG_MAIN_LOOP
31 #include "log.h"
32 //#endif
33
34
35 GUI::GUI(SDL_Surface * mainSurface): menuItem(new MenuItems())
36 {
37         windowList.push_back(new Menu());
38         Element::SetScreen(mainSurface);
39 }
40
41 GUI::~GUI()
42 {
43         // Clean up menuItem, if any
44
45         if (menuItem)
46                 delete menuItem;
47
48         // Clean up the rest
49
50         for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
51                 if (*i)
52                         delete *i;
53 }
54
55 void GUI::AddMenuTitle(const char * title)
56 {
57         menuItem->title = title;
58         menuItem->item.clear();
59 }
60
61 void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDLKey k/*= SDLK_UNKNOWN*/)
62 {
63         menuItem->item.push_back(NameAction(item, a, k));
64 }
65
66 void GUI::CommitItemsToMenu(void)
67 {
68 //We could just do a simple check here to see if more than one item is in the list,
69 //and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
70
71 //Right now, we just silently fail...
72         if (windowList.size() > 1)
73         {
74                 WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
75                 return;
76         }
77
78         ((Menu *)(*windowList.begin()))->Add(*menuItem);
79 }
80
81 void GUI::Run(void)
82 {
83         exitGUI = false;
84         showMouse = true;
85         SDL_Event event;
86         std::list<Element *>::iterator i;
87
88         SDL_EnableKeyRepeat(150, 75);
89
90         // Initial update... [Now handled correctly in the constructor]
91         for(i=windowList.begin(); i!=windowList.end(); i++)
92                 (*i)->Draw();
93
94         RenderScreenBuffer();
95
96         // Main loop
97         while (!exitGUI)
98         {
99                 if (SDL_PollEvent(&event))
100                 {
101 #ifdef DEBUG_MAIN_LOOP
102 WriteLog("An event was found!");
103 #endif
104                         if (event.type == SDL_USEREVENT)
105                         {
106 #ifdef DEBUG_MAIN_LOOP
107 WriteLog(" -- SDL_USEREVENT\n");
108 #endif
109 //Mebbe add another user event for screen refresh? Why not!
110                                 if (event.user.code == WINDOW_CLOSE)
111                                 {
112                                         for(i=windowList.begin(); i!=windowList.end(); i++)
113                                         {
114                                                 if (*i == (Element *)event.user.data1)
115                                                 {
116                                                         delete *i;
117                                                         windowList.erase(i);
118                                                         break;
119                                                 }
120                                         }
121                                 }
122                                 else if (event.user.code == MENU_ITEM_CHOSEN)
123                                 {
124                                         // Confused? Let me enlighten... What we're doing here is casting
125                                         // data1 as a pointer to a function which returns a Element pointer and
126                                         // which takes no parameters (the "(Element *(*)(void))" part), then
127                                         // derefencing it (the "*" in front of that) in order to call the
128                                         // function that it points to. Clear as mud? Yeah, I hate function
129                                         // pointers too, but what else are you gonna do?
130                                         Element * window = (*(Element *(*)(void))event.user.data1)();
131
132                                         if (window)
133                                                 windowList.push_back(window);
134
135                                         while (SDL_PollEvent(&event));  // Flush the event queue...
136
137                                         event.type = SDL_MOUSEMOTION;
138                                         int mx, my;
139                                         SDL_GetMouseState(&mx, &my);
140                                         event.motion.x = mx, event.motion.y = my;
141                                     SDL_PushEvent(&event);                      // & update mouse position...!
142
143                                         oldMouse.x = mouse.x, oldMouse.y = mouse.y;
144                                         mouse.x = mx, mouse.y = my;             // This prevents "mouse flash"...
145                                 }
146 //There's a *small* problem with the following approach--if a window and a bunch of
147 //child widgets send this message, we'll get a bunch of unnecessary refresh events...
148 //This could be controlled by having the main window refresh itself intelligently...
149
150 //What we could do instead is set a variable in Element and check it after the fact
151 //to see whether or not a refresh is needed.
152 //[This is what we do now.]
153
154 //Dirty rectangle is also possible...
155                                 else if (event.user.code == SCREEN_REFRESH_NEEDED)
156                                         RenderScreenBuffer();
157                         }
158                         else if (event.type == SDL_ACTIVEEVENT)
159                         {
160 //Need to do a screen refresh here...
161                                 if (event.active.state == SDL_APPMOUSEFOCUS)
162                                         showMouse = (event.active.gain ? true : false);
163
164                                 RenderScreenBuffer();
165                         }
166                         else if (event.type == SDL_KEYDOWN)
167                         {
168 #ifdef DEBUG_MAIN_LOOP
169 WriteLog(" -- SDL_KEYDOWN\n");
170 #endif
171                                 if (event.key.keysym.sym == SDLK_F1)
172                                         exitGUI = true;
173
174 //Not sure that this is the right way to handle this...
175 //Probably should only give this to the top level window...
176 //                              for(i=windowList.begin(); i!=windowList.end(); i++)
177 //                                      (*i)->HandleKey(event.key.keysym.sym);
178                                 windowList.back()->HandleKey(event.key.keysym.sym);
179                         }
180                         else if (event.type == SDL_MOUSEMOTION)
181                         {
182 #ifdef DEBUG_MAIN_LOOP
183 WriteLog(" -- SDL_MOUSEMOTION\n");
184 #endif
185 //This is for tracking a custom mouse cursor, which we're not doing--YET.
186                                 oldMouse.x = mouse.x, oldMouse.y = mouse.y;
187                                 mouse.x = event.motion.x, mouse.y = event.motion.y;
188
189 //Not sure that this is the right way to handle this...
190 //Right now, we should probably only do mouseover for the last item in the list...
191 //And now we do!
192 //Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
193 //Or maybe to just the ones that aren't completely obscured?
194 //Probably. Right now, a disk's close button that should be obscured by one sitting on
195 //top of it gets redrawn. Not good.
196                                 for(i=windowList.begin(); i!=windowList.end(); i++)
197                                         (*i)->HandleMouseMove(mouse.x, mouse.y);
198 //                              windowList.back()->HandleMouseMove(mouse.x, mouse.y);
199                         }
200                         else if (event.type == SDL_MOUSEBUTTONDOWN)
201                         {
202 #ifdef DEBUG_MAIN_LOOP
203 WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
204 #endif
205 //Not sure that this is the right way to handle this...
206 // What we should do here is ensure that whatever has been clicked on gets moved to the
207 // highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
208 //[DONE]
209
210 /*
211
212 We could do the following:
213
214 - Go through list and find which window has been clicked on (if any). If more
215   than one is clicked on, take the one highest in the Z order (closer to the end
216   of the list).
217
218 - If item is highest in Z order, pack click through to window and exit.
219
220 - Otherwise, restore backing store on each window in reverse order.
221
222 - Remove item clicked on from the list. Put removed item at the end of the list.
223
224 - Go through list and pass click through to each window in the list. Also do a
225   blit to backing store and a Draw() for each window.
226
227 Could also do a check (if not clicked on highest Z window) to see which windows
228 it overlaps and just do restore/redraw for those that overlap. To wit:
229
230 - Create new list containing only those windows that overlap the clicking on window.
231
232 - Go through list and do a blit to backing store and a Draw() for each window.
233
234 - Go through list and pass click through to each window in the list.
235
236 */
237
238 #if 0
239 #if 0
240                                 for(i=windowList.begin(); i!=windowList.end(); i++)
241                                         (*i)->HandleMouseButton(event.button.x, event.button.y, true);
242 #else
243 // We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
244 // to the 2nd...
245
246                                 // Walk backward through the list and see if a window was hit.
247                                 // This will automagically return us the window with the highest Z.
248
249                                 std::list<Element *>::reverse_iterator ri;
250                                 std::list<Element *>::iterator hit;// = windowList.end();
251
252                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
253                                 {
254                                         if ((*ri)->Inside(event.button.x, event.button.y))
255                                         {
256                                                 // Here's a bit of STL weirdness: Converting from a reverse
257                                                 // iterator to a regular iterator requires backing the iterator
258                                                 // up a position after grabbing it's base() OR going forward
259                                                 // one position with the reverse iterator before grabbing base().
260                                                 // Ugly, but it get the job done...
261                                                 hit = (++ri).base();
262                                                 // Put it back where we found it, so the tests following this
263                                                 // don't fail...
264                                                 ri--;
265                                                 break;
266                                         }
267                                 }
268
269                                 // If we hit the highest in the list, then pass the event through
270                                 // to the window for handling. if we hit no windows, then pass the
271                                 // event to all windows. Otherwise, we need to shuffle windows.
272
273 //NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
274                                 if (ri == windowList.rbegin())
275                                 {
276                                         for(i=windowList.begin(); i!=windowList.end(); i++)
277                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
278                                 }
279                                 else if (ri == windowList.rend())
280                                 {
281                                         for(i=windowList.begin(); i!=windowList.end(); i++)
282                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
283                                 }
284                                 else
285                                 {
286 // - Otherwise, restore backing store on each window in reverse order.
287                                         for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
288                                                 (*ri)->RestoreScreenFromBackstore();
289                                         // At this point, the screen has been restored...
290
291 // - Remove item clicked on from the list. Put removed item at the end of the list.
292                                         windowList.push_back(*hit);
293                                         windowList.erase(hit);
294 // - Go through list and pass click through to each window in the list. Also do a
295 //  blit to backing store and a Draw() for each window.
296                                         for(i=windowList.begin(); i!= windowList.end(); i++)
297                                         {
298                                                 // Grab bg into backstore
299                                                 (*i)->SaveScreenToBackstore();
300                                                 // Pass click
301                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
302                                                 // Draw?
303                                                 (*i)->Draw();
304                                         }
305                                 }
306 #endif
307 #endif
308 /*
309 A slightly different way to handle this would be to loop through all windows, compare
310 all those above it to see if they obscure it; if so then subdivide it's update rectangle
311 to eliminate drawing the parts that aren't shown. The beauty of this approach is that
312 you don't have to care what order the windows are drawn in and you don't need to worry
313 about the order of restoring the backing store.
314
315 You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
316 correct, but that's not too terrible.
317
318 Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
319 */
320                                 std::list<Element *>::reverse_iterator ri;
321                                 bool movedWindow = false;
322
323                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
324                                 {
325                                         if ((*ri)->Inside(event.button.x, event.button.y))
326                                         {
327                                                 // Remove item clicked on from the list & put removed item at the
328                                                 // end of the list, thus putting the window at the top of the Z
329                                                 // order. But IFF window is not already topmost!
330                                                 if (ri != windowList.rbegin())
331                                                 {
332                                                         windowList.push_back(*ri);
333                                                         // Here's a bit of STL weirdness: Converting from a reverse
334                                                         // iterator to a regular iterator requires backing the iterator
335                                                         // up a position after grabbing it's base() OR going forward
336                                                         // one position with the reverse iterator before grabbing base().
337                                                         // Ugly, but it get the job done...
338                                                         windowList.erase((++ri).base());
339                                                         movedWindow = true;
340                                                 }
341
342                                                 break;
343                                         }
344                                 }
345
346 //Small problem here: we should only pass the *hit* to the topmost window and pass
347 //*misses* to everyone else... Otherwise, you can have overlapping draggable windows
348 //and be able to drag both by clicking on a point that intersects both...
349 //(though that may be an interesting way to handle things!)
350                                 // Pass the click on to all windows
351                                 for(i=windowList.begin(); i!=windowList.end(); i++)
352                                         (*i)->HandleMouseButton(event.button.x, event.button.y, true);
353
354 //                              // & bail if nothing changed...
355                                 if (movedWindow)
356 //                                      return;
357 {
358                                 // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
359 //One way to optimize this would be to only reset coverage lists from the point in
360 //the Z order where the previous window was.
361                                 for(i=windowList.begin(); i!=windowList.end(); i++)
362                                 {
363                                         (*i)->ResetCoverageList();
364
365                                         // This looks odd, but it's just a consequence of iterator weirdness.
366                                         // Otherwise we could just stick a j+1 in the for loop below. :-P
367                                         std::list<Element *>::iterator j = i;
368                                         j++;
369
370                                         for(; j!=windowList.end(); j++)
371                                                 (*i)->AdjustCoverageList((*j)->GetExtents());
372
373 //                                      (*i)->HandleMouseButton(event.button.x, event.button.y, true);
374                                         (*i)->Draw();
375                                 }
376 }
377                         }
378                         else if (event.type == SDL_MOUSEBUTTONUP)
379                         {
380 #ifdef DEBUG_MAIN_LOOP
381 WriteLog(" -- SDL_MOUSEBUTTONUP\n");
382 #endif
383 //Not sure that this is the right way to handle this...
384                                 for(i=windowList.begin(); i!=windowList.end(); i++)
385                                         (*i)->HandleMouseButton(event.button.x, event.button.y, false);
386 //I think we should only do topmost here...
387 //Or should we???
388 //                              windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
389                         }
390 #ifdef DEBUG_MAIN_LOOP
391 else
392         WriteLog(" -- Unknown event\n");
393 #endif
394
395                         if (Element::ScreenNeedsRefreshing())
396                         {
397 #ifdef DEBUG_MAIN_LOOP
398 WriteLog("Screen refresh called!\n");
399 #endif
400                                 RenderScreenBuffer();
401                                 Element::ScreenWasRefreshed();
402                         }
403                 }
404         }
405
406         SDL_EnableKeyRepeat(0, 0);
407 //      return false;
408 }
409
410 void GUI::Stop(void)
411 {
412         exitGUI = true;
413 }