1 // gui.cpp:  Top level GUI for SWF player, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 //   2011 Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 //
20 
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24 
25 #include "gui.h"
26 
27 #include <vector>
28 #include <algorithm>
29 #include <iostream>
30 
31 #include "MovieClip.h"
32 #include "Renderer.h"
33 #include "sound_handler.h"
34 #include "movie_root.h"
35 #include "VM.h"
36 #include "DisplayObject.h"
37 #include "GnashEnums.h"
38 #include "RunResources.h"
39 #include "StreamProvider.h"
40 #include "ScreenShotter.h"
41 #include "Movie.h"
42 
43 #ifdef GNASH_FPS_DEBUG
44 #include "ClockTime.h"
45 #include <boost/format.hpp>
46 #endif
47 
48 /// Define this to make sure each frame is fully rendered from ground up
49 /// even if no motion has been detected in the movie.
50 //#define FORCE_REDRAW 1
51 
52 /// Define this if you want to debug the *detection* of region updates only.
53 /// This will disable region updates for the backend (GUI+renderer) completely
54 /// so that only the last region (red frame) will be visible. However, this
55 /// slows down rendering as each frame is fully re-rendered. If you want to
56 /// debug the GUI part, however (see if blitting the region works), then you
57 /// probably won't define this.
58 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
59 //#define REGION_UPDATES_DEBUGGING_FULL_REDRAW 1
60 #endif
61 
62 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
63 // a runtime check would make the { x; } block conditionally executed
64 #define IF_DEBUG_REGION_UPDATES(...) do { if (_showUpdatedRegions) {  __VA_ARGS__; } } while(0);
65 #else
66 #define IF_DEBUG_REGION_UPDATES(x)
67 #endif
68 
69 // Define this to have gnash print the mouse pointer coordinates
70 // as the mouse moves. See also ENABLE_KEYBOARD_MOUSE_MOVEMENTS
71 // to have more control over mouse pointer.
72 //
73 //#define DEBUG_MOUSE_COORDINATES 1
74 
75 namespace gnash {
76 
77 struct Gui::Display
78 {
Displaygnash::Gui::Display79     Display(Gui& g, movie_root& r) : _g(g), _r(r) {}
operator ()gnash::Gui::Display80     void operator()() const {
81 		InvalidatedRanges world_ranges;
82 		world_ranges.setWorld();
83 		_g.setInvalidatedRegions(world_ranges);
84         _g.display(&_r);
85     }
86 private:
87     Gui& _g;
88     movie_root& _r;
89 };
90 
Gui(RunResources & r)91 Gui::Gui(RunResources& r) :
92     _loop(true),
93     _xid(0),
94     _width(1),
95     _height(1),
96     _runResources(r),
97     _interval(0),
98     _redraw_flag(true),
99     _fullscreen(false),
100     _mouseShown(true),
101     _maxAdvances(0),
102     _advances(0),
103     _xscale(1.0f),
104     _yscale(1.0f),
105     _xoffset(0),
106     _yoffset(0)
107 #ifdef GNASH_FPS_DEBUG
108     ,fps_counter(0)
109     ,fps_counter_total(0)
110     ,fps_timer(0)
111     ,fps_timer_interval(0.0)
112     ,frames_dropped(0)
113 #endif
114     ,_movieDef(nullptr)
115     ,_stage(nullptr)
116     ,_stopped(false)
117     ,_started(false)
118     ,_showUpdatedRegions(false)
119 
120     // NOTE: it's important that _systemClock is constructed
121     //       before and destroyed after _virtualClock !
122     ,_systemClock()
123     ,_virtualClock(_systemClock)
124 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
125     ,_xpointer(0)
126     ,_ypointer(0)
127     ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
128     ,_keyboardMouseMovementsStep(1)
129 #endif
130 {
131 
132 }
133 
Gui(unsigned long xid,float scale,bool loop,RunResources & r)134 Gui::Gui(unsigned long xid, float scale, bool loop, RunResources& r)
135 	:
136     _loop(loop),
137     _xid(xid),
138     _width(1),
139     _height(1),
140     _runResources(r),
141     _interval(0),
142     _redraw_flag(true),
143     _fullscreen(false),
144     _mouseShown(true),
145     _maxAdvances(0),
146     _advances(0),
147     _xscale(scale),
148     _yscale(scale),
149     _xoffset(0), // TODO: x and y offset will need update !
150     _yoffset(0)
151 #ifdef GNASH_FPS_DEBUG
152     ,fps_counter(0)
153     ,fps_counter_total(0)
154     ,fps_timer(0)
155     ,fps_timer_interval(0.0)
156     ,frames_dropped(0)
157 #endif
158     ,_movieDef(nullptr)
159     ,_stage(nullptr)
160     ,_stopped(false)
161     ,_started(false)
162     ,_showUpdatedRegions(false)
163 
164     // NOTE: it's important that _systemClock is constructed
165     //       before and destroyed after _virtualClock !
166     ,_systemClock()
167     ,_virtualClock(_systemClock)
168 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
169     ,_xpointer(0)
170     ,_ypointer(0)
171     ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
172     ,_keyboardMouseMovementsStep(1)
173 #endif
174 {
175 }
176 
~Gui()177 Gui::~Gui()
178 {
179     if ( _movieDef.get() ) {
180         log_debug("~Gui - _movieDef refcount: %d", _movieDef->get_ref_count());
181     }
182 
183 #ifdef GNASH_FPS_DEBUG
184     if ( fps_timer_interval ) {
185         std::cerr << "Total frame advances/drops: "
186                   << fps_counter_total << "/" << frames_dropped << std::endl;
187     }
188 #endif
189 }
190 
191 void
setClipboard(const std::string &)192 Gui::setClipboard(const std::string&)
193 {
194     LOG_ONCE(log_unimpl(_("Clipboard not yet supported in this GUI")));
195 }
196 
197 void
setFullscreen()198 Gui::setFullscreen()
199 {
200     log_unimpl(_("Fullscreen not yet supported in this GUI"));
201 }
202 
203 void
resizeWindow(int,int)204 Gui::resizeWindow(int /*width*/, int /*height*/)
205 {
206     log_unimpl(_("Window resize not yet supported in this GUI"));
207 }
208 
209 void
unsetFullscreen()210 Gui::unsetFullscreen()
211 {
212     log_unimpl(_("Fullscreen not yet supported in this GUI"));
213 }
214 
215 void
quit()216 Gui::quit()
217 {
218     // Take a screenshot of the last frame if required.
219     if (_screenShotter.get() && _renderer.get()) {
220         Display dis(*this, *_stage);
221         _screenShotter->last(*_renderer, &dis);
222     }
223 
224     quitUI();
225 }
226 
227 void
hideMenu()228 Gui::hideMenu()
229 {
230     LOG_ONCE(log_unimpl(_("Menu show/hide not yet supported in this GUI")));
231 }
232 
233 bool
showMouse(bool)234 Gui::showMouse(bool /* show */)
235 {
236     LOG_ONCE(log_unimpl(_("Mouse show/hide not yet supported in this GUI")));
237     return true;
238 }
239 
240 void
showMenu(bool)241 Gui::showMenu(bool /* show */)
242 {
243     LOG_ONCE(log_unimpl(_("Menu show/hide not yet supported in this GUI")));
244 }
245 
246 void
allowScale(bool allow)247 Gui::allowScale(bool allow)
248 {
249     if (!_stage) {
250         log_error("Gui::allowScale called before a movie_root was available");
251         return;
252     }
253 
254     if (allow) _stage->setStageScaleMode(movie_root::SCALEMODE_SHOWALL);
255     else _stage->setStageScaleMode(movie_root::SCALEMODE_NOSCALE);
256 }
257 
258 void
toggleFullscreen()259 Gui::toggleFullscreen()
260 {
261     /// Sends request to Gnash core to change display state.
262     if (_fullscreen) {
263         _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
264     } else {
265         _stage->setStageDisplayState(movie_root::DISPLAYSTATE_FULLSCREEN);
266     }
267 }
268 
269 void
restart()270 Gui::restart()
271 {
272     _stage->reset();
273     _started = false;
274     start();
275 }
276 
277 void
updateStageMatrix()278 Gui::updateStageMatrix()
279 {
280     if (!_stage) {
281         // When VM initializes, we'll get a call to resize_view, which
282         // would call us again.
283         log_error(_("Can't update stage matrix till VM is initialized"));
284         return;
285     }
286 
287     assert(_stage); // when VM is initialized this should hold
288 
289     float swfwidth = _movieDef->get_width_pixels();
290     float swfheight = _movieDef->get_height_pixels();
291 
292     // Fetch scale mode
293     movie_root::ScaleMode scaleMode = _stage->getStageScaleMode();
294 
295     switch (scaleMode) {
296         case movie_root::SCALEMODE_NOSCALE:
297             _xscale = _yscale = 1.0f;
298             break;
299 
300         case movie_root::SCALEMODE_SHOWALL:
301             // set new scale value ( user-pixel / pseudo-pixel ). Do
302             // not divide by zero, or we end up with an invalid
303             // stage matrix that returns nan values.
304             _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
305             _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
306 
307             // Scale proportionally, using smallest scale
308             if (_xscale < _yscale) {
309                 _yscale = _xscale;
310             } else if (_yscale < _xscale) {
311                 _xscale = _yscale;
312             }
313             break;
314 
315         case movie_root::SCALEMODE_NOBORDER:
316             // set new scale value ( user-pixel / pseudo-pixel )
317             _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
318             _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
319 
320             // Scale proportionally, using biggest scale
321             if (_xscale > _yscale) {
322                 _yscale = _xscale;
323             } else if (_yscale > _xscale) {
324                 _xscale = _yscale;
325             }
326             break;
327 
328         case movie_root::SCALEMODE_EXACTFIT:
329             // NOTE: changing aspect ratio is valid!
330             _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
331             _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
332             break;
333 
334         default:
335             log_error(_("Invalid scaleMode %d"), scaleMode);
336             break;
337     }
338 
339     _xoffset=0;
340     _yoffset=0;
341 
342     // Fetch align mode
343     movie_root::StageAlign align = _stage->getStageAlignment();
344     movie_root::StageHorizontalAlign halign = align.first;
345     movie_root::StageVerticalAlign valign = align.second;
346 
347     // Handle horizontal alignment
348     switch ( halign ) {
349       case movie_root::STAGE_H_ALIGN_L:
350       {
351           // _xoffset=0 is fine
352           break;
353       }
354 
355       case movie_root::STAGE_H_ALIGN_R:
356       {
357           // Offsets in pixels
358           float defWidth = swfwidth *= _xscale;
359           float diffWidth = _width-defWidth;
360           _xoffset = diffWidth;
361           break;
362       }
363 
364       case movie_root::STAGE_V_ALIGN_C:
365       {
366           // Offsets in pixels
367           float defWidth = swfwidth *= _xscale;
368           float diffWidth = _width-defWidth;
369           _xoffset = diffWidth/2.0;
370           break;
371       }
372 
373       default:
374       {
375           log_error(_("Invalid horizontal align %d"), valign);
376           break;
377       }
378     }
379 
380     // Handle vertical alignment
381     switch ( valign ) {
382       case movie_root::STAGE_V_ALIGN_T:
383       {
384           // _yoffset=0 is fine
385           break;
386       }
387 
388       case movie_root::STAGE_V_ALIGN_B:
389       {
390           float defHeight = swfheight *= _yscale;
391           float diffHeight = _height-defHeight;
392           _yoffset = diffHeight;
393           break;
394       }
395 
396       case movie_root::STAGE_V_ALIGN_C:
397       {
398           float defHeight = swfheight *= _yscale;
399           float diffHeight = _height-defHeight;
400           _yoffset = diffHeight/2.0;
401           break;
402       }
403 
404       default:
405       {
406           log_error(_("Invalid vertical align %d"), valign);
407           break;
408       }
409     }
410 
411     //log_debug("updateStageMatrix: scaleMode:%d, valign:%d, halign:%d",
412     //scaleMode, valign, halign);
413 
414     // TODO: have a generic set_matrix ?
415     if (_renderer.get()) {
416         _renderer->set_scale(_xscale, _yscale);
417         _renderer->set_translation(_xoffset, _yoffset);
418     } else {
419         //log_debug("updateStageMatrix: could not signal updated stage
420         //matrix to renderer (no renderer registered)");
421     }
422 
423     // trigger redraw
424     //_redraw_flag |= (_width!=width) || (_height!=height);
425     _redraw_flag = true; // this fixes bug #21971
426 }
427 
428 
429 void
resize_view(int width,int height)430 Gui::resize_view(int width, int height)
431 {
432     GNASH_REPORT_FUNCTION;
433 
434     assert(width > 0);
435     assert(height > 0);
436 
437     if (_stage && _started) {
438         _stage->setDimensions(width, height);
439     }
440 
441     _width = width;
442     _height = height;
443     _validbounds.setTo(0, 0, _width, _height);
444 
445     updateStageMatrix();
446 
447     if ( _stage && _started ) {
448         display(_stage);
449     }
450 }
451 
452 
453 void
toggleSound()454 Gui::toggleSound()
455 {
456     assert (_stage);
457     // @todo since we registered the sound handler, shouldn't we know
458     //       already what it is ?!
459 #ifdef USE_SOUND
460     sound::sound_handler* s = _stage->runResources().soundHandler();
461 
462     if (!s) return;
463 
464     if (s->is_muted()) s->unmute();
465     else s->mute();
466 #endif  // USE_SOUND
467 }
468 
469 void
notifyMouseMove(int ux,int uy)470 Gui::notifyMouseMove(int ux, int uy)
471 {
472     movie_root* m = _stage;
473 
474     if ( ! _started ) return;
475 
476     if ( _stopped ) return;
477 
478     // A stage pseudopixel is user pixel / _xscale wide
479     std::int32_t x = (ux-_xoffset) / _xscale;
480 
481     // A stage pseudopixel is user pixel / _xscale high
482     std::int32_t y = (uy-_yoffset) / _yscale;
483 
484 #ifdef DEBUG_MOUSE_COORDINATES
485     log_debug("mouse @ %d,%d", x, y);
486 #endif
487 
488     if ( m->mouseMoved(x, y) ) {
489         // any action triggered by the
490         // event required screen refresh
491         display(m);
492     }
493 
494     DisplayObject* activeEntity = m->getActiveEntityUnderPointer();
495     if ( activeEntity ) {
496         if ( activeEntity->isSelectableTextField() ) {
497             setCursor(CURSOR_INPUT);
498         } else if ( activeEntity->allowHandCursor() ) {
499             setCursor(CURSOR_HAND);
500         } else {
501             setCursor(CURSOR_NORMAL);
502         }
503     } else {
504         setCursor(CURSOR_NORMAL);
505     }
506 
507 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
508     _xpointer = ux;
509     _ypointer = uy;
510 #endif
511 
512 
513 }
514 
515 void
notifyMouseWheel(int delta)516 Gui::notifyMouseWheel(int delta)
517 {
518     movie_root* m = _stage;
519     assert(m);
520 
521     if (!_started) return;
522     if (_stopped) return;
523 
524     if (m->mouseWheel(delta)) {
525         // any action triggered by the
526         // event required screen refresh
527         display(m);
528     }
529 }
530 
531 void
notifyMouseClick(bool mouse_pressed)532 Gui::notifyMouseClick(bool mouse_pressed)
533 {
534     movie_root* m = _stage;
535     assert(m);
536 
537     if (!_started) return;
538     if (_stopped) return;
539 
540     if (m->mouseClick(mouse_pressed)) {
541         // any action triggered by the
542         // event required screen refresh
543         display(m);
544     }
545 }
546 
547 void
refreshView()548 Gui::refreshView()
549 {
550     movie_root* m = _stage;
551 
552     if ( ! _started ) return;
553 
554     assert(m);
555     _redraw_flag=true;
556     display(m);
557 }
558 
559 
560 void
notify_key_event(gnash::key::code k,int modifier,bool pressed)561 Gui::notify_key_event(gnash::key::code k, int modifier, bool pressed)
562 {
563 
564     // Handle GUI shortcuts
565     if (pressed) {
566         if (k == gnash::key::ESCAPE) {
567             if (isFullscreen()) {
568                 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
569             }
570         }
571 
572         if (modifier & gnash::key::GNASH_MOD_CONTROL) {
573             switch (k) {
574               case gnash::key::o:
575               case gnash::key::O:
576                   takeScreenShot();
577                   break;
578               case gnash::key::r:
579               case gnash::key::R:
580                   restart();
581                   break;
582               case gnash::key::p:
583               case gnash::key::P:
584                   pause();
585                   break;
586               case gnash::key::l:
587               case gnash::key::L:
588                   refreshView();
589                   break;
590               case gnash::key::q:
591               case gnash::key::Q:
592               case gnash::key::w:
593               case gnash::key::W:
594                   quit();
595                   break;
596               case gnash::key::f:
597               case gnash::key::F:
598                   toggleFullscreen();
599                   break;
600               case gnash::key::h:
601               case gnash::key::H:
602                   showUpdatedRegions(!showUpdatedRegions());
603                   break;
604               case gnash::key::MINUS:
605               {
606                   // Max interval allowed: 1 second (1FPS)
607                   const size_t ni = std::min<size_t>(_interval + 2, 1000u);
608                   setInterval(ni);
609                   break;
610               }
611               case gnash::key::PLUS:
612               {
613                   // Min interval allowed: 1/100 second (100FPS)
614                   const size_t ni = std::max<size_t>(_interval - 2, 10u);
615                   setInterval(ni);
616                   break;
617               }
618               case gnash::key::EQUALS:
619               {
620                   if (_stage) {
621                       const float fps = _stage->getRootMovie().frameRate();
622                       // Min interval allowed: 1/100 second (100FPS)
623                       const size_t ni = 1000.0/fps;
624                       setInterval(ni);
625                   }
626                   break;
627               }
628               default:
629                   break;
630             }
631 
632 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
633             if ( _keyboardMouseMovements ) {
634                 int step = _keyboardMouseMovementsStep;
635                 // x5 if SHIFT is pressed
636                 if (modifier & gnash::key::GNASH_MOD_SHIFT) step *= 5;
637                 switch (k) {
638                   case gnash::key::UP:
639                   {
640                       int newx = _xpointer;
641                       int newy = _ypointer-step;
642                       if ( newy < 0 ) newy=0;
643                       notifyMouseMove(newx, newy);
644                       break;
645                   }
646                   case gnash::key::DOWN:
647                   {
648                       int newx = _xpointer;
649                       int newy = _ypointer+step;
650                       if ( newy >= _height ) newy = _height-1;
651                       notifyMouseMove(newx, newy);
652                       break;
653                   }
654                   case gnash::key::LEFT:
655                   {
656                       int newx = _xpointer-step;
657                       int newy = _ypointer;
658                       if ( newx < 0 ) newx = 0;
659                       notifyMouseMove(newx, newy);
660                       break;
661                   }
662                   case gnash::key::RIGHT:
663                   {
664                       const int newy = _ypointer;
665                       int newx = _xpointer + step;
666                       if ( newx >= _width ) newx = _width-1;
667                       notifyMouseMove(newx, newy);
668                       break;
669                   }
670                   default:
671                       break;
672                 }
673             }
674 #endif // ENABLE_KEYBOARD_MOUSE_MOVEMENTS
675         }
676     }
677 
678     if (!_started) return;
679 
680     if (_stopped) return;
681 
682     if (_stage->keyEvent(k, pressed)) {
683         // any action triggered by the
684         // event required screen refresh
685         display(_stage);
686     }
687 
688 }
689 
690 bool
display(movie_root * m)691 Gui::display(movie_root* m)
692 {
693     assert(m == _stage); // why taking this arg ??
694 
695     assert(_started);
696 
697     InvalidatedRanges changed_ranges;
698     bool redraw_flag;
699 
700     // Should the frame be rendered completely, even if it did not change?
701 #ifdef FORCE_REDRAW
702     redraw_flag = true;
703 #else
704     redraw_flag = _redraw_flag || want_redraw();
705 #endif
706 
707     // reset class member if we do a redraw now
708     if (redraw_flag) _redraw_flag=false;
709 
710     // Find out the surrounding frame of all characters which
711     // have been updated. This just checks what region of the stage has changed
712     // due to ActionScript code, the timeline or user events. The GUI can still
713     // choose to render a different part of the stage.
714     //
715     if (!redraw_flag) {
716 
717         // choose snapping ranges factor
718         changed_ranges.setSnapFactor(1.3f);
719 
720         // Use multi ranges only when GUI/Renderer supports it
721         // (Useless CPU overhead, otherwise)
722         changed_ranges.setSingleMode(!want_multiple_regions());
723 
724         // scan through all sprites to compute invalidated bounds
725         m->add_invalidated_bounds(changed_ranges, false);
726 
727         // grow ranges by a 2 pixels to avoid anti-aliasing issues
728         changed_ranges.growBy(40.0f / _xscale);
729 
730         // optimize ranges
731         changed_ranges.combineRanges();
732     }
733 
734     // TODO: Remove this and want_redraw to avoid confusion!?
735     if (redraw_flag)  {
736         changed_ranges.setWorld();
737     }
738 
739     // DEBUG ONLY:
740     // This is a good place to inspect the invalidated bounds state. Enable
741     // the following block (and parts of it) if you need to.
742 #if 0
743     {
744         // This may print a huge amount of information, but is useful to analyze
745         // the (visible) object structure of the movie and the flags of the
746         // characters. For example, a characters should have set the
747         // m_child_invalidated flag if at least one of it's childs has the
748         // invalidated flag set.
749         log_debug("DUMPING CHARACTER TREE");
750 
751         InfoTree tr;
752         InfoTree::iterator top = tr.begin();
753         _stage->getMovieInfo(tr, top);
754 
755         for (InfoTree::iterator i = tr.begin(), e = tr.end();
756              i != e; ++i) {
757             std::cout << std::string(tr.depth(i) * 2, ' ') << i->first << ": " <<
758                 i->second << std::endl;
759         }
760 
761 
762         // less verbose, and often necessary: see the exact coordinates of the
763         // invalidated bounds (mainly to see if it's NULL or something else).
764         std::cout << "Calculated changed ranges: " << changed_ranges << "\n";
765     }
766 #endif
767 
768     // Avoid drawing of stopped movies
769     if ( ! changed_ranges.isNull() ) { // use 'else'?
770         // Tell the GUI(!) that we only need to update this
771         // region. Note the GUI can do whatever it wants with
772         // this information. It may simply ignore the bounds
773         // (which will normally lead into a complete redraw),
774         // or it may extend or shrink the bounds as it likes. So,
775         // by calling set_invalidated_bounds we have no guarantee
776         // that only this part of the stage is rendered again.
777 #ifdef REGION_UPDATES_DEBUGGING_FULL_REDRAW
778         // redraw the full screen so that only the
779         // *new* invalidated region is visible
780         // (helps debugging)
781         InvalidatedRanges world_ranges;
782         world_ranges.setWorld();
783         setInvalidatedRegions(world_ranges);
784 #else
785         setInvalidatedRegions(changed_ranges);
786 #endif
787 
788         // TODO: should this be called even if we're late ?
789         beforeRendering();
790 
791         // Render the frame, if not late.
792         // It's up to the GUI/renderer combination
793         // to do any clipping, if desired.
794         m->display();
795 
796         // show invalidated region using a red rectangle
797         // (Flash debug style)
798         IF_DEBUG_REGION_UPDATES (
799             if (_renderer.get() && !changed_ranges.isWorld()) {
800                 for (size_t rno = 0; rno < changed_ranges.size(); rno++) {
801                     const geometry::Range2d<int>& bounds =
802                         changed_ranges.getRange(rno);
803 
804                     float xmin = bounds.getMinX();
805                     float xmax = bounds.getMaxX();
806                     float ymin = bounds.getMinY();
807                     float ymax = bounds.getMaxY();
808 
809                     const std::vector<point> box = {
810                         point(xmin, ymin),
811                         point(xmax, ymin),
812                         point(xmax, ymax),
813                         point(xmin, ymax)
814                     };
815 
816                     _renderer->draw_poly(box, rgba(0,0,0,0), rgba(255,0,0,255),
817                                          SWFMatrix(), false);
818 
819                 }
820             }
821         );
822 
823         // show frame on screen
824         renderBuffer();
825     };
826 
827     return true;
828 }
829 
830 void
play()831 Gui::play()
832 {
833     if ( ! _stopped ) return;
834 
835     _stopped = false;
836     if ( ! _started ) {
837         start();
838     } else {
839         assert (_stage);
840 #ifdef USE_SOUND
841         // @todo since we registered the sound handler, shouldn't we know
842         //       already what it is ?!
843         sound::sound_handler* s = _stage->runResources().soundHandler();
844         if ( s ) s->unpause();
845 #endif  // USE_SOUND
846         // log_debug("Starting virtual clock");
847         _virtualClock.resume();
848     }
849 
850     playHook ();
851 }
852 
853 void
stop()854 Gui::stop()
855 {
856     // _stage must be registered before this is called.
857     assert(_stage);
858 
859     if ( _stopped ) return;
860     if ( isFullscreen() ) unsetFullscreen();
861 
862     _stopped = true;
863 
864     // @todo since we registered the sound handler, shouldn't we know
865     //       already what it is ?!
866     sound::sound_handler* s = _stage->runResources().soundHandler();
867     if ( s ) s->pause();
868 
869     // log_debug("Pausing virtual clock");
870     _virtualClock.pause();
871 
872     stopHook();
873 }
874 
875 void
pause()876 Gui::pause()
877 {
878     if (_stopped) {
879         play();
880         return;
881     }
882 
883     // TODO: call stop() instead ?
884     // The only thing I see is that ::stop exits full-screen,
885     // but I'm not sure that's intended behaviour
886 
887     // @todo since we registered the sound handler, shouldn't we know
888     //       already what it is ?!
889     sound::sound_handler* s = _stage->runResources().soundHandler();
890     if (s) s->pause();
891     _stopped = true;
892 
893     // log_debug("Pausing virtual clock");
894     _virtualClock.pause();
895 
896     stopHook();
897 }
898 
899 void
start()900 Gui::start()
901 {
902     assert ( ! _started );
903     if (_stopped) {
904         log_error(_("GUI is in stop mode, won't start application"));
905         return;
906     }
907 
908     // Initializes the stage with a Movie and the passed flash vars.
909     _stage->init(_movieDef.get(), _flashVars);
910 
911     bool background = true; // ??
912     _stage->set_background_alpha(background ? 1.0f : 0.05f);
913 
914     // to properly update stageMatrix if scaling is given
915     resize_view(_width, _height);
916 
917     // @todo since we registered the sound handler, shouldn't we know
918     //       already what it is ?!
919 #ifdef USE_SOUND
920     sound::sound_handler* s = _stage->runResources().soundHandler();
921     if ( s ) {
922         if ( ! _audioDump.empty() ) {
923             s->setAudioDump(_audioDump);
924         }
925         s->unpause();
926     }
927 #endif  // USE_SOUND
928     _started = true;
929 
930     // log_debug("Starting virtual clock");
931     _virtualClock.resume();
932 
933 }
934 
935 bool
advanceMovie(bool doDisplay)936 Gui::advanceMovie(bool doDisplay)
937 {
938     if (isStopped()) {
939         return false;
940     }
941 
942     if (!_started) {
943         start();
944     }
945 
946     Display dis(*this, *_stage);
947     gnash::movie_root* m = _stage;
948 
949     // Define REVIEW_ALL_FRAMES to have *all* frames
950     // consequentially displayed. Useful for debugging.
951     //#define REVIEW_ALL_FRAMES 1
952 
953 #ifndef REVIEW_ALL_FRAMES
954     // Advance movie by one frame
955     const bool advanced = m->advance();
956 #else
957     const size_t cur_frame = m->getRootMovie().get_current_frame();
958     const size_t tot_frames = m->getRootMovie().get_frame_count();
959     const bool advanced = m->advance();
960 
961     m->getRootMovie().ensureFrameLoaded(tot_frames);
962     m->goto_frame(cur_frame + 1);
963     m->getRootMovie().setPlayState(gnash::MovieClip::PLAYSTATE_PLAY);
964     // log_debug("Frame %d", m->getRootMovie().get_current_frame());
965 #endif
966 
967 #ifdef GNASH_FPS_DEBUG
968     // will be a no-op if fps_timer_interval is zero
969     if (advanced) {
970         fpsCounterTick();
971     }
972 #endif
973 
974     if (doDisplay && visible()) {
975         display(m);
976     }
977 
978     if (!loops()) {
979         // can be 0 on malformed SWF
980         const size_t curframe = m->getRootMovie().get_current_frame();
981         const MovieClip& si = m->getRootMovie();
982         if (curframe + 1 >= si.get_frame_count()) {
983             quit();
984         }
985     }
986 
987     if (_screenShotter.get() && _renderer.get()) {
988         _screenShotter->screenShot(*_renderer, _advances, doDisplay ? nullptr : &dis);
989     }
990 
991     // Only increment advances and check for exit condition when we've
992     // really changed frame.
993     if (advanced) {
994         /// Quit if we've reached the frame advance limit.
995         if (_maxAdvances && (_advances > _maxAdvances)) {
996             quit();
997         }
998         ++_advances;
999     }
1000 
1001 	return advanced;
1002 }
1003 
1004 void
setScreenShotter(std::unique_ptr<ScreenShotter> ss)1005 Gui::setScreenShotter(std::unique_ptr<ScreenShotter> ss)
1006 {
1007     _screenShotter.reset(ss.release());
1008 }
1009 
1010 void
takeScreenShot()1011 Gui::takeScreenShot()
1012 {
1013     if (!_screenShotter.get()) {
1014         // If no ScreenShotter exists, none was requested at startup.
1015         // We use a default filename pattern.
1016         URL url(_runResources.streamProvider().baseURL());
1017         std::string::size_type p = url.path().rfind('/');
1018         const std::string& name = (p == std::string::npos) ? url.path() :
1019             url.path().substr(p + 1);
1020         const std::string& filename = "screenshot-" + name + "-%f";
1021         _screenShotter.reset(new ScreenShotter(filename, GNASH_FILETYPE_PNG));
1022     }
1023     assert (_screenShotter.get());
1024     _screenShotter->now();
1025 }
1026 
1027 void
setCursor(gnash_cursor_type)1028 Gui::setCursor(gnash_cursor_type /*newcursor*/)
1029 {
1030     /* do nothing */
1031 }
1032 
1033 bool
want_redraw()1034 Gui::want_redraw()
1035 {
1036     return false;
1037 }
1038 
1039 void
setInvalidatedRegion(const SWFRect &)1040 Gui::setInvalidatedRegion(const SWFRect& /*bounds*/)
1041 {
1042     /* do nothing */
1043 }
1044 
1045 void
setInvalidatedRegions(const InvalidatedRanges & ranges)1046 Gui::setInvalidatedRegions(const InvalidatedRanges& ranges)
1047 {
1048     // fallback to single regions
1049     geometry::Range2d<int> full = ranges.getFullArea();
1050 
1051     SWFRect bounds;
1052 
1053     if (full.isFinite()) {
1054         bounds = SWFRect(full.getMinX(), full.getMinY(),
1055                 full.getMaxX(), full.getMaxY());
1056     }
1057     else if (full.isWorld()) {
1058         bounds.set_world();
1059     }
1060 
1061     setInvalidatedRegion(bounds);
1062 }
1063 
1064 #ifdef USE_SWFTREE
1065 
1066 std::unique_ptr<movie_root::InfoTree>
getMovieInfo() const1067 Gui::getMovieInfo() const
1068 {
1069     std::unique_ptr<movie_root::InfoTree> tr;
1070 
1071     if (!_stage) {
1072         return tr;
1073     }
1074 
1075     tr.reset(new movie_root::InfoTree());
1076 
1077     // Top nodes for the tree:
1078     // 1. VM information
1079     // 2. "Stage" information
1080     // 3. ...
1081 
1082     movie_root::InfoTree::iterator topIter = tr->begin();
1083     movie_root::InfoTree::iterator firstLevelIter;
1084 
1085     VM& vm = _stage->getVM();
1086 
1087     std::ostringstream os;
1088 
1089     //
1090     /// VM top level
1091     //
1092     os << "SWF " << vm.getSWFVersion();
1093     topIter = tr->insert(topIter, std::make_pair("Root SWF version", os.str()));
1094 
1095     // This short-cut is to avoid a bug in movie_root's getMovieInfo,
1096     // which relies on the availability of a _rootMovie for doing
1097     // it's work, while we don't set it if we didn't start..
1098     //
1099     if (! _started) {
1100         topIter = tr->insert(topIter, std::make_pair("Stage properties",
1101                     "not constructed yet"));
1102         return tr;
1103     }
1104 
1105     movie_root& stage = vm.getRoot();
1106     stage.getMovieInfo(*tr, topIter);
1107 
1108     //
1109     /// Mouse entities
1110     //
1111     topIter = tr->insert(topIter, std::make_pair("Mouse Entities", ""));
1112 
1113     const DisplayObject* ch;
1114     ch = stage.getActiveEntityUnderPointer();
1115     if (ch) {
1116         std::stringstream ss;
1117         ss << ch->getTarget() << " (" + typeName(*ch)
1118            << " - depth:" << ch->get_depth()
1119            << " - useHandCursor:" << ch->allowHandCursor()
1120            << ")";
1121     	firstLevelIter = tr->append_child(topIter,
1122                 std::make_pair("Active entity under mouse pointer", ss.str()));
1123     }
1124 
1125     ch = stage.getEntityUnderPointer();
1126     if (ch) {
1127         std::stringstream ss;
1128         ss << ch->getTarget() << " (" + typeName(*ch)
1129            << " - depth:" << ch->get_depth()
1130            << ")";
1131         firstLevelIter = tr->append_child(topIter,
1132             std::make_pair("Topmost entity under mouse pointer", ss.str()));
1133     }
1134 
1135     ch = stage.getDraggingCharacter();
1136     if (ch) {
1137         std::stringstream ss;
1138         ss << ch->getTarget() << " (" + typeName(*ch)
1139            << " - depth:" << ch->get_depth() << ")";
1140     	firstLevelIter = tr->append_child(topIter,
1141                 std::make_pair("Dragging character: ", ss.str()));
1142     }
1143 
1144     //
1145     /// GC row
1146     //
1147     topIter = tr->insert(topIter, std::make_pair("GC Statistics", ""));
1148     GC::CollectablesCount cc;
1149     _stage->gc().countCollectables(cc);
1150 
1151     const std::string lbl = "GC managed ";
1152     for (auto& countinfo : cc) {
1153         const std::string& typ = countinfo.first;
1154         std::ostringstream ss;
1155         ss << countinfo.second;
1156         firstLevelIter = tr->append_child(topIter,
1157                     std::make_pair(lbl + typ, ss.str()));
1158     }
1159 
1160     tr->sort(firstLevelIter.begin(), firstLevelIter.end());
1161 
1162     return tr;
1163 }
1164 
1165 #endif
1166 
1167 #ifdef GNASH_FPS_DEBUG
1168 void
fpsCounterTick()1169 Gui::fpsCounterTick()
1170 {
1171 
1172   // increment this *before* the early return so that
1173   // frame count on exit is still valid
1174   ++fps_counter_total;
1175 
1176   if (! fps_timer_interval) {
1177       return;
1178   }
1179 
1180   std::uint64_t current_timer = clocktime::getTicks();
1181 
1182   // TODO: keep fps_timer_interval in milliseconds to avoid the multiplication
1183   //       at each fpsCounterTick call...
1184   std::uint64_t interval_ms = (std::uint64_t)(fps_timer_interval * 1000.0);
1185 
1186   if (fps_counter_total==1) {
1187     fps_timer = current_timer;
1188     fps_start_timer = current_timer;
1189   }
1190 
1191   ++fps_counter;
1192 
1193   if (current_timer - fps_timer >= interval_ms) {
1194 
1195     float secs = (current_timer - fps_timer) / 1000.0;
1196     float secs_total = (current_timer - fps_start_timer)/1000.0;
1197 
1198     float rate = fps_counter/secs;
1199 
1200     if (secs > 10000000) {
1201       // the timers are unsigned, so when the clock runs "backwards" it leads
1202       // to a very high difference value. In theory, this should never happen
1203       // with ticks, but it does on my machine (which may have a hw problem?).
1204       std::cerr << "Time glitch detected, need to restart FPS counters, sorry..." << std::endl;
1205 
1206       fps_timer = current_timer;
1207       fps_start_timer = current_timer;
1208       fps_counter_total = 0;
1209       fps_counter = 0;
1210       return;
1211     }
1212 
1213     // first FPS message?
1214     if (fps_timer == fps_start_timer) {     // they're ints, so we can compare
1215       fps_rate_min = rate;
1216       fps_rate_max = rate;
1217     } else {
1218       fps_rate_min = std::min<float>(fps_rate_min, rate);
1219       fps_rate_max = std::max<float>(fps_rate_max, rate);
1220     }
1221 
1222     float avg = fps_counter_total / secs_total;
1223 
1224     //log_debug("Effective frame rate: %0.2f fps", (float)(fps_counter/secs));
1225     std::cerr << boost::format("Effective frame rate: %0.2f fps "
1226                                "(min %0.2f, avg %0.2f, max %0.2f, "
1227                                "%u frames in %0.1f secs total, "
1228                                "dropped %u)") % rate %
1229                                fps_rate_min % avg % fps_rate_max %
1230                                fps_counter_total % secs_total %
1231                                frames_dropped << std::endl;
1232 
1233     fps_counter = 0;
1234     fps_timer = current_timer;
1235 
1236   }
1237 
1238 }
1239 #endif
1240 
1241 void
addFlashVars(Gui::VariableMap & from)1242 Gui::addFlashVars(Gui::VariableMap& from)
1243 {
1244     for (auto& variable : from) {
1245         _flashVars[variable.first] = variable.second;
1246     }
1247 }
1248 
1249 void
setMovieDefinition(movie_definition * md)1250 Gui::setMovieDefinition(movie_definition* md)
1251 {
1252     assert(!_movieDef);
1253     _movieDef = md;
1254 }
1255 
1256 void
setStage(movie_root * stage)1257 Gui::setStage(movie_root* stage)
1258 {
1259     assert(stage);
1260     assert(!_stage);
1261     _stage = stage;
1262 }
1263 
1264 bool
yesno(const std::string & question)1265 Gui::yesno(const std::string& question)
1266 {
1267     log_error(_("This GUI didn't override 'yesno', assuming 'yes' answer to "
1268                 "question: %s"), question);
1269     return true;
1270 }
1271 
1272 void
setQuality(Quality q)1273 Gui::setQuality(Quality q)
1274 {
1275     if (!_stage) {
1276         log_error(_("Gui::setQuality called before a movie_root was available"));
1277         return;
1278     }
1279     _stage->setQuality(q);
1280 }
1281 
1282 Quality
getQuality() const1283 Gui::getQuality() const
1284 {
1285     if (!_stage) {
1286 	log_error(_("Gui::getQuality called before a movie_root was available"));
1287 	// just a guess..
1288 	return QUALITY_HIGH;
1289     }
1290     return _stage->getQuality();
1291 }
1292 
1293 }
1294 
1295 // local Variables:
1296 // mode: C++
1297 // indent-tabs-mode: nil
1298 // End:
1299