Have Gui::advanceMovie return true or false based on whether frame advancement occurr...
[gnash.git] / gui / gui.cpp
blob121290509712dc7f59c7a470803c2804105d7e9b
1 // gui.cpp: Top level GUI for SWF player, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // 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.
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
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
25 #include "gui.h"
27 #include <vector>
28 #include <cstdio>
29 #include <cstring>
30 #include <algorithm>
31 #include <boost/algorithm/string/replace.hpp>
32 #include <boost/lexical_cast.hpp>
34 #include "MovieClip.h"
35 #include "Renderer.h"
36 #include "sound_handler.h"
37 #include "movie_root.h"
38 #include "VM.h"
39 #include "DisplayObject.h"
40 #include "tu_file.h"
41 #include "gnash.h"
42 #include "RunResources.h"
43 #include "StreamProvider.h"
45 #ifdef GNASH_FPS_DEBUG
46 #include "ClockTime.h"
47 #include <boost/format.hpp>
48 #endif
50 /// Define this to make sure each frame is fully rendered from ground up
51 /// even if no motion has been detected in the movie.
52 //#define FORCE_REDRAW 1
54 /// Define this if you want to debug the *detection* of region updates only.
55 /// This will disable region updates for the backend (GUI+renderer) completely
56 /// so that only the last region (red frame) will be visible. However, this
57 /// slows down rendering as each frame is fully re-rendered. If you want to
58 /// debug the GUI part, however (see if blitting the region works), then you
59 /// probably won't define this.
60 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
61 //#define REGION_UPDATES_DEBUGGING_FULL_REDRAW 1
62 #endif
64 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
65 // a runtime check would make the { x; } block conditionally executed
66 #define IF_DEBUG_REGION_UPDATES(x) { if (_showUpdatedRegions) { x } }
67 #else
68 #define IF_DEBUG_REGION_UPDATES(x)
69 #endif
71 // Define this to have gnash print the mouse pointer coordinates
72 // as the mouse moves. See also ENABLE_KEYBOARD_MOUSE_MOVEMENTS
73 // to have more control over mouse pointer.
75 //#define DEBUG_MOUSE_COORDINATES 1
77 // Define the following macro if you want to skip rendering
78 // when late on FPS time.
79 // This is an experimental feature, so it's off by default
80 //#define SKIP_RENDERING_IF_LATE 1
83 namespace gnash {
85 Gui::Gui(RunResources& r) :
86 _loop(true),
87 _xid(0),
88 _width(1),
89 _height(1),
90 _runResources(r),
91 _interval(0),
92 _redraw_flag(true),
93 _fullscreen(false),
94 _mouseShown(true),
95 _maxAdvances(0),
96 _advances(0),
97 _xscale(1.0f),
98 _yscale(1.0f),
99 _xoffset(0),
100 _yoffset(0)
101 #ifdef GNASH_FPS_DEBUG
102 ,fps_counter(0)
103 ,fps_counter_total(0)
104 ,fps_timer(0)
105 ,fps_timer_interval(0.0)
106 ,frames_dropped(0)
107 #endif
108 ,_movieDef(0)
109 ,_stage(0)
110 ,_stopped(false)
111 ,_started(false)
112 ,_showUpdatedRegions(false)
114 // NOTE: it's important that _systemClock is constructed
115 // before and destroyed after _virtualClock !
116 ,_systemClock()
117 ,_virtualClock(_systemClock)
118 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
119 ,_xpointer(0)
120 ,_ypointer(0)
121 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
122 ,_keyboardMouseMovementsStep(1)
123 #endif
128 Gui::Gui(unsigned long xid, float scale, bool loop, RunResources& r)
130 _loop(loop),
131 _xid(xid),
132 _width(1),
133 _height(1),
134 _runResources(r),
135 _interval(0),
136 _redraw_flag(true),
137 _fullscreen(false),
138 _mouseShown(true),
139 _maxAdvances(0),
140 _advances(0),
141 _xscale(scale),
142 _yscale(scale),
143 _xoffset(0), // TODO: x and y offset will need update !
144 _yoffset(0)
145 #ifdef GNASH_FPS_DEBUG
146 ,fps_counter(0)
147 ,fps_counter_total(0)
148 ,fps_timer(0)
149 ,fps_timer_interval(0.0)
150 ,frames_dropped(0)
151 #endif
152 ,_movieDef(0)
153 ,_stage(0)
154 ,_stopped(false)
155 ,_started(false)
156 ,_showUpdatedRegions(false)
158 // NOTE: it's important that _systemClock is constructed
159 // before and destroyed after _virtualClock !
160 ,_systemClock()
161 ,_virtualClock(_systemClock)
162 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
163 ,_xpointer(0)
164 ,_ypointer(0)
165 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
166 ,_keyboardMouseMovementsStep(1)
167 #endif
171 Gui::~Gui()
173 if ( _movieDef.get() ) log_debug("~Gui - _movieDef refcount: %d", _movieDef->get_ref_count());
175 #ifdef GNASH_FPS_DEBUG
176 if ( fps_timer_interval ) {
177 std::cerr << "Total frame advances/drops: "
178 << fps_counter_total << "/" << frames_dropped << std::endl;
180 #endif
183 void
184 Gui::setFullscreen()
186 log_unimpl(_("Fullscreen not yet supported in this GUI"));
189 void
190 Gui::resizeWindow(int /*width*/, int /*height*/)
192 log_unimpl(_("Window resize not yet supported in this GUI"));
195 void
196 Gui::unsetFullscreen()
198 log_unimpl(_("Fullscreen not yet supported in this GUI"));
201 void
202 Gui::quit()
204 log_debug(__PRETTY_FUNCTION__);
206 // Take a screenshot of the last frame if required.
207 if (_screenShotter.get()) {
208 _screenShotter->last();
211 quitUI();
214 void
215 Gui::hideMenu()
217 log_unimpl(_("Menu hiding not yet supported in this GUI"));
220 bool
221 Gui::showMouse(bool /* show */)
223 LOG_ONCE(log_unimpl(_("Mouse show/hide not yet supported in this GUI")));
224 return true;
227 void
228 Gui::showMenu(bool /* show */)
230 LOG_ONCE(log_unimpl(_("menushow not yet supported in this GUI")));
233 void
234 Gui::allowScale(bool allow)
236 if (!_stage) {
237 log_error("Gui::allowScale called before a movie_root was available");
238 return;
241 if (allow) _stage->setStageScaleMode(movie_root::SCALEMODE_SHOWALL);
242 else _stage->setStageScaleMode(movie_root::SCALEMODE_NOSCALE);
245 void
246 Gui::toggleFullscreen()
248 /// Sends request to Gnash core to change display state.
249 if (_fullscreen) {
250 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
252 else {
253 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_FULLSCREEN);
257 void
258 Gui::restart()
260 _stage->reset();
261 _started = false;
262 start();
265 void
266 Gui::updateStageMatrix()
268 if (!_stage) {
269 // When VM initializes, we'll get a call to resize_view, which
270 // would call us again.
271 //log_debug("Can't update stage matrix till VM is initialized");
272 return;
275 assert(_stage); // when VM is initialized this should hold
277 float swfwidth = _movieDef->get_width_pixels();
278 float swfheight = _movieDef->get_height_pixels();
280 // Fetch scale mode
281 movie_root::ScaleMode scaleMode = _stage->getStageScaleMode();
283 switch (scaleMode) {
284 case movie_root::SCALEMODE_NOSCALE:
285 _xscale = _yscale = 1.0f;
286 break;
288 case movie_root::SCALEMODE_SHOWALL:
289 // set new scale value ( user-pixel / pseudo-pixel ). Do
290 // not divide by zero, or we end up with an invalid
291 // stage matrix that returns nan values.
292 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
293 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
295 // Scale proportionally, using smallest scale
296 if (_xscale < _yscale) {
297 _yscale = _xscale;
299 else if (_yscale < _xscale) {
300 _xscale = _yscale;
302 break;
304 case movie_root::SCALEMODE_NOBORDER:
305 // set new scale value ( user-pixel / pseudo-pixel )
306 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
307 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
309 // Scale proportionally, using biggest scale
310 if (_xscale > _yscale) {
311 _yscale = _xscale;
313 else if (_yscale > _xscale) {
314 _xscale = _yscale;
316 break;
318 case movie_root::SCALEMODE_EXACTFIT:
319 // NOTE: changing aspect ratio is valid!
320 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
321 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
322 break;
324 default:
325 log_error("Invalid scaleMode %d", scaleMode);
326 break;
329 _xoffset=0;
330 _yoffset=0;
332 // Fetch align mode
333 movie_root::StageAlign align = _stage->getStageAlignment();
334 movie_root::StageHorizontalAlign halign = align.first;
335 movie_root::StageVerticalAlign valign = align.second;
337 // Handle horizontal alignment
338 switch ( halign ) {
339 case movie_root::STAGE_H_ALIGN_L:
341 // _xoffset=0 is fine
342 break;
345 case movie_root::STAGE_H_ALIGN_R:
347 // Offsets in pixels
348 float defWidth = swfwidth *= _xscale;
349 float diffWidth = _width-defWidth;
350 _xoffset = diffWidth;
351 break;
354 case movie_root::STAGE_V_ALIGN_C:
356 // Offsets in pixels
357 float defWidth = swfwidth *= _xscale;
358 float diffWidth = _width-defWidth;
359 _xoffset = diffWidth/2.0;
360 break;
363 default:
365 log_error("Invalid horizontal align %d", valign);
366 break;
370 // Handle vertical alignment
371 switch ( valign ) {
372 case movie_root::STAGE_V_ALIGN_T:
374 // _yoffset=0 is fine
375 break;
378 case movie_root::STAGE_V_ALIGN_B:
380 float defHeight = swfheight *= _yscale;
381 float diffHeight = _height-defHeight;
382 _yoffset = diffHeight;
383 break;
386 case movie_root::STAGE_V_ALIGN_C:
388 float defHeight = swfheight *= _yscale;
389 float diffHeight = _height-defHeight;
390 _yoffset = diffHeight/2.0;
391 break;
394 default:
396 log_error("Invalid vertical align %d", valign);
397 break;
401 //log_debug("updateStageMatrix: scaleMode:%d, valign:%d, halign:%d",
402 //scaleMode, valign, halign);
404 // TODO: have a generic set_matrix ?
405 if (_renderer.get()) {
406 _renderer->set_scale(_xscale, _yscale);
407 _renderer->set_translation(_xoffset, _yoffset);
408 } else {
409 //log_debug("updateStageMatrix: could not signal updated stage
410 //matrix to renderer (no renderer registered)");
413 // trigger redraw
414 //_redraw_flag |= (_width!=width) || (_height!=height);
415 _redraw_flag = true; // this fixes bug #21971
419 void
420 Gui::resize_view(int width, int height)
423 assert(width>0);
424 assert(height>0);
426 if (_stage && _started) {
427 _stage->setDimensions(width, height);
430 _width = width;
431 _height = height;
432 _validbounds.setTo(0, 0, _width, _height);
434 updateStageMatrix();
436 if ( _stage && _started ) display(_stage);
440 void
441 Gui::toggleSound()
443 assert (_stage);
444 // @todo since we registered the sound handler, shouldn't we know
445 // already what it is ?!
446 sound::sound_handler* s = _stage->runResources().soundHandler();
448 if (!s) return;
450 if (s->is_muted()) s->unmute();
451 else s->mute();
455 void
456 Gui::notifyMouseMove(int ux, int uy)
458 movie_root* m = _stage;
460 if ( ! _started ) return;
462 if ( _stopped ) return;
464 // A stage pseudopixel is user pixel / _xscale wide
465 boost::int32_t x = (ux-_xoffset) / _xscale;
467 // A stage pseudopixel is user pixel / _xscale high
468 boost::int32_t y = (uy-_yoffset) / _yscale;
470 #ifdef DEBUG_MOUSE_COORDINATES
471 log_debug(_("mouse @ %d,%d"), x, y);
472 #endif
474 if ( m->mouseMoved(x, y) )
476 // any action triggered by the
477 // event required screen refresh
478 display(m);
481 DisplayObject* activeEntity = m->getActiveEntityUnderPointer();
482 if ( activeEntity )
484 if ( activeEntity->isSelectableTextField() )
486 setCursor(CURSOR_INPUT);
488 else if ( activeEntity->allowHandCursor() )
490 setCursor(CURSOR_HAND);
492 else
494 setCursor(CURSOR_NORMAL);
497 else
499 setCursor(CURSOR_NORMAL);
502 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
503 _xpointer = ux;
504 _ypointer = uy;
505 #endif
510 void
511 Gui::notifyMouseWheel(int delta)
513 movie_root* m = _stage;
514 assert(m);
516 if (!_started) return;
517 if (_stopped) return;
519 if (m->mouseWheel(delta)) {
520 // any action triggered by the
521 // event required screen refresh
522 display(m);
526 void
527 Gui::notifyMouseClick(bool mouse_pressed)
529 movie_root* m = _stage;
530 assert(m);
532 if (!_started) return;
533 if (_stopped) return;
535 if (m->mouseClick(mouse_pressed)) {
536 // any action triggered by the
537 // event required screen refresh
538 display(m);
542 void
543 Gui::refreshView()
545 movie_root* m = _stage;
547 if ( ! _started ) return;
549 assert(m);
550 _redraw_flag=true;
551 display(m);
555 void
556 Gui::notify_key_event(gnash::key::code k, int modifier, bool pressed)
559 /* Handle GUI shortcuts */
560 if (pressed) {
561 if (k == gnash::key::ESCAPE) {
562 if (isFullscreen()) {
563 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
567 if (modifier & gnash::key::GNASH_MOD_CONTROL) {
568 switch (k) {
569 case gnash::key::o:
570 case gnash::key::O:
571 takeScreenShot();
572 break;
573 case gnash::key::r:
574 case gnash::key::R:
575 restart();
576 break;
577 case gnash::key::p:
578 case gnash::key::P:
579 pause();
580 break;
581 case gnash::key::l:
582 case gnash::key::L:
583 refreshView();
584 break;
585 case gnash::key::q:
586 case gnash::key::Q:
587 case gnash::key::w:
588 case gnash::key::W:
589 quit();
590 break;
591 case gnash::key::f:
592 case gnash::key::F:
593 toggleFullscreen();
594 break;
595 case gnash::key::h:
596 case gnash::key::H:
597 showUpdatedRegions(!showUpdatedRegions());
598 break;
599 case gnash::key::MINUS:
601 // Max interval allowed: 1 second (1FPS)
602 const size_t ni = std::min<size_t>(_interval + 2, 1000u);
603 setInterval(ni);
604 break;
606 case gnash::key::PLUS:
608 // Min interval allowed: 1/100 second (100FPS)
609 const size_t ni = std::max<size_t>(_interval - 2, 10u);
610 setInterval(ni);
611 break;
613 case gnash::key::EQUALS:
615 if (_stage) {
616 const float fps = _stage->frameRate();
617 // Min interval allowed: 1/100 second (100FPS)
618 const size_t ni = 1000.0/fps;
619 setInterval(ni);
621 break;
623 default:
624 break;
627 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
628 if ( _keyboardMouseMovements ) {
629 int step = _keyboardMouseMovementsStep;
630 // x5 if SHIFT is pressed
631 if (modifier & gnash::key::GNASH_MOD_SHIFT) step *= 5;
632 switch (k) {
633 case gnash::key::UP:
635 int newx = _xpointer;
636 int newy = _ypointer-step;
637 if ( newy < 0 ) newy=0;
638 notifyMouseMove(newx, newy);
639 break;
641 case gnash::key::DOWN:
643 int newx = _xpointer;
644 int newy = _ypointer+step;
645 if ( newy >= _height ) newy = _height-1;
646 notifyMouseMove(newx, newy);
647 break;
649 case gnash::key::LEFT:
651 int newx = _xpointer-step;
652 int newy = _ypointer;
653 if ( newx < 0 ) newx = 0;
654 notifyMouseMove(newx, newy);
655 break;
657 case gnash::key::RIGHT:
659 const int newy = _ypointer;
660 int newx = _xpointer + step;
661 if ( newx >= _width ) newx = _width-1;
662 notifyMouseMove(newx, newy);
663 break;
665 default:
666 break;
669 #endif // ENABLE_KEYBOARD_MOUSE_MOVEMENTS
673 if (!_started) return;
675 if (_stopped) return;
677 if (_stage->keyEvent(k, pressed)) {
678 // any action triggered by the
679 // event required screen refresh
680 display(_stage);
685 bool
686 Gui::display(movie_root* m)
688 assert(m == _stage); // why taking this arg ??
690 assert(_started);
692 InvalidatedRanges changed_ranges;
693 bool redraw_flag;
695 // Should the frame be rendered completely, even if it did not change?
696 #ifdef FORCE_REDRAW
697 redraw_flag = true;
698 #else
699 redraw_flag = _redraw_flag || want_redraw();
700 #endif
702 // reset class member if we do a redraw now
703 if (redraw_flag) _redraw_flag=false;
705 // Find out the surrounding frame of all characters which
706 // have been updated. This just checks what region of the stage has changed
707 // due to ActionScript code, the timeline or user events. The GUI can still
708 // choose to render a different part of the stage.
710 if (!redraw_flag) {
712 // choose snapping ranges factor
713 changed_ranges.setSnapFactor(1.3f);
715 // Use multi ranges only when GUI/Renderer supports it
716 // (Useless CPU overhead, otherwise)
717 changed_ranges.setSingleMode(!want_multiple_regions());
719 // scan through all sprites to compute invalidated bounds
720 m->add_invalidated_bounds(changed_ranges, false);
722 // grow ranges by a 2 pixels to avoid anti-aliasing issues
723 changed_ranges.growBy(40.0f / _xscale);
725 // optimize ranges
726 changed_ranges.combineRanges();
730 // TODO: Remove this and want_redraw to avoid confusion!?
731 if (redraw_flag) {
732 changed_ranges.setWorld();
735 // DEBUG ONLY:
736 // This is a good place to inspect the invalidated bounds state. Enable
737 // the following block (and parts of it) if you need to.
738 #if 0
740 // This may print a huge amount of information, but is useful to analyze
741 // the (visible) object structure of the movie and the flags of the
742 // characters. For example, a characters should have set the
743 // m_child_invalidated flag if at least one of it's childs has the
744 // invalidated flag set.
745 log_debug("DUMPING CHARACTER TREE");
747 InfoTree tr;
748 InfoTree::iterator top = tr.begin();
749 _stage->getMovieInfo(tr, top);
751 for (InfoTree::iterator i = tr.begin(), e = tr.end();
752 i != e; ++i) {
753 std::cout << std::string(tr.depth(i) * 2, ' ') << i->first << ": " <<
754 i->second << std::endl;
758 // less verbose, and often necessary: see the exact coordinates of the
759 // invalidated bounds (mainly to see if it's NULL or something else).
760 std::cout << "Calculated changed ranges: " << changed_ranges << "\n";
762 #endif
764 // Avoid drawing of stopped movies
765 if ( ! changed_ranges.isNull() ) // use 'else'?
767 // Tell the GUI(!) that we only need to update this
768 // region. Note the GUI can do whatever it wants with
769 // this information. It may simply ignore the bounds
770 // (which will normally lead into a complete redraw),
771 // or it may extend or shrink the bounds as it likes. So,
772 // by calling set_invalidated_bounds we have no guarantee
773 // that only this part of the stage is rendered again.
774 #ifdef REGION_UPDATES_DEBUGGING_FULL_REDRAW
775 // redraw the full screen so that only the
776 // *new* invalidated region is visible
777 // (helps debugging)
778 InvalidatedRanges world_ranges;
779 world_ranges.setWorld();
780 setInvalidatedRegions(world_ranges);
781 #else
782 setInvalidatedRegions(changed_ranges);
783 #endif
785 // TODO: should this be called even if we're late ?
786 beforeRendering();
788 // Render the frame, if not late.
789 // It's up to the GUI/renderer combination
790 // to do any clipping, if desired.
791 m->display();
793 // show invalidated region using a red rectangle
794 // (Flash debug style)
795 IF_DEBUG_REGION_UPDATES (
796 if (_renderer.get() && !changed_ranges.isWorld())
799 for (size_t rno = 0; rno < changed_ranges.size(); rno++) {
801 const geometry::Range2d<int>& bounds =
802 changed_ranges.getRange(rno);
804 point corners[4];
805 float xmin = bounds.getMinX();
806 float xmax = bounds.getMaxX();
807 float ymin = bounds.getMinY();
808 float ymax = bounds.getMaxY();
810 corners[0].x = xmin;
811 corners[0].y = ymin;
812 corners[1].x = xmax;
813 corners[1].y = ymin;
814 corners[2].x = xmax;
815 corners[2].y = ymax;
816 corners[3].x = xmin;
817 corners[3].y = ymax;
818 SWFMatrix no_transform;
819 _renderer->draw_poly(corners, 4,
820 rgba(0,0,0,0), rgba(255,0,0,255), no_transform, false);
826 // show frame on screen
827 renderBuffer();
831 return true;
834 void
835 Gui::play()
837 if ( ! _stopped ) return;
839 _stopped = false;
840 if ( ! _started ) start();
841 else
843 assert (_stage);
844 // @todo since we registered the sound handler, shouldn't we know
845 // already what it is ?!
846 sound::sound_handler* s = _stage->runResources().soundHandler();
847 if ( s ) s->unpause();
849 log_debug("Starting virtual clock");
850 _virtualClock.resume();
853 playHook ();
856 void
857 Gui::stop()
859 // _stage must be registered before this is called.
860 assert(_stage);
862 if ( _stopped ) return;
863 if ( isFullscreen() ) unsetFullscreen();
865 _stopped = true;
867 // @todo since we registered the sound handler, shouldn't we know
868 // already what it is ?!
869 sound::sound_handler* s = _stage->runResources().soundHandler();
870 if ( s ) s->pause();
872 log_debug("Pausing virtual clock");
873 _virtualClock.pause();
875 stopHook();
878 void
879 Gui::pause()
881 if (_stopped) {
882 play();
883 return;
886 // TODO: call stop() instead ?
887 // The only thing I see is that ::stop exits full-screen,
888 // but I'm not sure that's intended behaviour
890 // @todo since we registered the sound handler, shouldn't we know
891 // already what it is ?!
892 sound::sound_handler* s = _stage->runResources().soundHandler();
893 if (s) s->pause();
894 _stopped = true;
896 log_debug("Pausing virtual clock");
897 _virtualClock.pause();
899 stopHook();
902 void
903 Gui::start()
905 assert ( ! _started );
906 if (_stopped) {
907 log_debug("Gui is in stop mode, won't start application");
908 return;
911 // Initializes the stage with a Movie and the passed flash vars and
912 // Scriptable vars for ExternalInterface.
913 _stage->init(_movieDef.get(), _flashVars, _scriptableVars);
915 bool background = true; // ??
916 _stage->set_background_alpha(background ? 1.0f : 0.05f);
918 // @todo since we registered the sound handler, shouldn't we know
919 // already what it is ?!
920 sound::sound_handler* s = _stage->runResources().soundHandler();
921 if ( s ) s->unpause();
922 _started = true;
924 // to properly update stageMatrix if scaling is given
925 resize_view(_width, _height);
927 log_debug("Starting virtual clock");
928 _virtualClock.resume();
932 bool
933 Gui::advanceMovie()
936 if (isStopped()) {
937 return false;
940 if (!_started) {
941 start();
944 gnash::movie_root* m = _stage;
946 // Define REVIEW_ALL_FRAMES to have *all* frames
947 // consequentially displayed. Useful for debugging.
948 //#define REVIEW_ALL_FRAMES 1
950 #ifndef REVIEW_ALL_FRAMES
951 // Advance movie by one frame
952 const bool advanced = m->advance();
953 #else
954 const size_t cur_frame = m->getRootMovie()->get_current_frame();
955 const size_t tot_frames = m->getRootMovie()->get_frame_count();
956 const bool advanced = m->advance();
958 m->getRootMovie.ensureFrameLoaded(tot_frames);
959 m->goto_frame(cur_frame + 1);
960 m->set_play_state(gnash::MovieClip::PLAYSTATE_PLAY);
961 log_debug(_("Frame %d"), m->get_current_frame());
962 #endif
964 #ifdef GNASH_FPS_DEBUG
965 // will be a no-op if fps_timer_interval is zero
966 if (advanced) {
967 fpsCounterTick();
969 #endif
972 // TODO: ask stage about doDisplay ?
973 // - if it didn't advance might need to check updateAfterEvent
974 bool doDisplay = true;
976 #ifdef SKIP_RENDERING_IF_LATE
977 // We want to skip rendering IFF it's time to advance again.
978 // We'll ask the stage about it
979 if (_stage->timeToNextFrame() <= 0) {
981 // or should it be if advanced ?
982 if (doDisplay) {
983 // TODO: take note of a frame drop (count them)
984 //log_debug("Frame rendering dropped due to being late");
985 #ifdef GNASH_FPS_DEBUG
986 ++frames_dropped;
987 #endif
989 doDisplay = false;
991 #endif // ndef SKIP_RENDERING_IF_LATE
993 if (doDisplay) display(m);
995 if (!loops()) {
996 size_t curframe = m->get_current_frame(); // can be 0 on malformed SWF
997 const gnash::MovieClip& si = m->getRootMovie();
998 if (curframe + 1 >= si.get_frame_count()) {
999 quit();
1003 if (_screenShotter.get()) {
1004 _screenShotter->screenShot(_advances);
1007 // Only increment advances and check for exit condition when we've
1008 // really changed frame.
1009 if (advanced) {
1010 /// Quit if we've reached the frame advance limit.
1011 if (_maxAdvances && (_advances > _maxAdvances)) {
1012 quit();
1014 ++_advances;
1017 return advanced;
1020 void
1021 Gui::takeScreenShot()
1023 if (!_screenShotter.get()) {
1024 // If no ScreenShotter exists, none was requested at startup.
1025 // We use a default filename pattern.
1026 URL url(_runResources.streamProvider().originalURL());
1027 std::string::size_type p = url.path().rfind('/');
1028 const std::string& name = (p == std::string::npos) ? url.path() :
1029 url.path().substr(p + 1);
1030 const std::string& filename = "screenshot-" + name + "-%f";
1031 _screenShotter.reset(new ScreenShotter(_renderer, filename));
1033 assert (_screenShotter.get());
1034 _screenShotter->now();
1037 void
1038 Gui::requestScreenShots(const ScreenShotter::FrameList& l, bool last,
1039 const std::string& filename)
1041 // Nothing to do if there is no renderer or if no frames should be
1042 // saved.
1043 if (!_renderer.get() || (l.empty() && !last)) {
1044 return;
1047 _screenShotter.reset(new ScreenShotter(_renderer, filename));
1048 if (last) _screenShotter->lastFrame();
1049 _screenShotter->setFrames(l);
1053 void
1054 Gui::setCursor(gnash_cursor_type /*newcursor*/)
1056 /* do nothing */
1059 bool
1060 Gui::want_redraw()
1062 return false;
1065 void
1066 Gui::setInvalidatedRegion(const SWFRect& /*bounds*/)
1068 /* do nothing */
1071 void
1072 Gui::setInvalidatedRegions(const InvalidatedRanges& ranges)
1074 // fallback to single regions
1075 geometry::Range2d<int> full = ranges.getFullArea();
1077 SWFRect bounds;
1079 if (full.isFinite()) {
1080 bounds = SWFRect(full.getMinX(), full.getMinY(),
1081 full.getMaxX(), full.getMaxY());
1083 else if (full.isWorld()) {
1084 bounds.set_world();
1087 setInvalidatedRegion(bounds);
1090 #ifdef USE_SWFTREE
1092 std::auto_ptr<movie_root::InfoTree>
1093 Gui::getMovieInfo() const
1095 std::auto_ptr<movie_root::InfoTree> tr;
1097 if (!_stage) {
1098 return tr;
1101 tr.reset(new movie_root::InfoTree());
1103 // Top nodes for the tree:
1104 // 1. VM information
1105 // 2. "Stage" information
1106 // 3. ...
1108 movie_root::InfoTree::iterator topIter = tr->begin();
1109 movie_root::InfoTree::iterator firstLevelIter;
1111 VM& vm = _stage->getVM();
1113 std::ostringstream os;
1116 /// VM top level
1118 os << "SWF " << vm.getSWFVersion();
1119 topIter = tr->insert(topIter, std::make_pair("VM version", os.str()));
1121 // This short-cut is to avoid a bug in movie_root's getMovieInfo,
1122 // which relies on the availability of a _rootMovie for doing
1123 // it's work, while we don't set it if we didn't start..
1125 if (! _started) {
1126 topIter = tr->insert(topIter, std::make_pair("Stage properties",
1127 "not constructed yet"));
1128 return tr;
1131 movie_root& stage = vm.getRoot();
1132 stage.getMovieInfo(*tr, topIter);
1135 /// Mouse entities
1137 topIter = tr->insert(topIter, std::make_pair("Mouse Entities", ""));
1139 const DisplayObject* ch;
1140 ch = stage.getActiveEntityUnderPointer();
1141 if (ch) {
1142 std::stringstream ss;
1143 ss << ch->getTarget() << " (" + typeName(*ch)
1144 << " - depth:" << ch->get_depth()
1145 << " - useHandCursor:" << ch->allowHandCursor()
1146 << ")";
1147 firstLevelIter = tr->append_child(topIter,
1148 std::make_pair("Active entity under mouse pointer", ss.str()));
1151 ch = stage.getEntityUnderPointer();
1152 if (ch) {
1153 std::stringstream ss;
1154 ss << ch->getTarget() << " (" + typeName(*ch)
1155 << " - depth:" << ch->get_depth()
1156 << ")";
1157 firstLevelIter = tr->append_child(topIter,
1158 std::make_pair("Topmost entity under mouse pointer", ss.str()));
1161 ch = stage.getDraggingCharacter();
1162 if (ch) {
1163 std::stringstream ss;
1164 ss << ch->getTarget() << " (" + typeName(*ch)
1165 << " - depth:" << ch->get_depth() << ")";
1166 firstLevelIter = tr->append_child(topIter,
1167 std::make_pair("Dragging character: ", ss.str()));
1171 /// GC row
1173 topIter = tr->insert(topIter, std::make_pair("GC Statistics", ""));
1174 GC::CollectablesCount cc;
1175 _stage->gc().countCollectables(cc);
1177 const std::string lbl = "GC managed ";
1178 for (GC::CollectablesCount::iterator i=cc.begin(), e=cc.end(); i!=e; ++i) {
1179 const std::string& typ = i->first;
1180 std::ostringstream ss;
1181 ss << i->second;
1182 firstLevelIter = tr->append_child(topIter,
1183 std::make_pair(lbl + typ, ss.str()));
1186 tr->sort(firstLevelIter.begin(), firstLevelIter.end());
1188 return tr;
1191 #endif
1193 #ifdef GNASH_FPS_DEBUG
1194 void
1195 Gui::fpsCounterTick()
1198 // increment this *before* the early return so that
1199 // frame count on exit is still valid
1200 ++fps_counter_total;
1202 if (! fps_timer_interval) {
1203 return;
1206 boost::uint64_t current_timer = clocktime::getTicks();
1208 // TODO: keep fps_timer_interval in milliseconds to avoid the multiplication
1209 // at each fpsCounterTick call...
1210 boost::uint64_t interval_ms = (boost::uint64_t)(fps_timer_interval * 1000.0);
1212 if (fps_counter_total==1) {
1213 fps_timer = current_timer;
1214 fps_start_timer = current_timer;
1217 ++fps_counter;
1219 if (current_timer - fps_timer >= interval_ms) {
1221 float secs = (current_timer - fps_timer) / 1000.0;
1222 float secs_total = (current_timer - fps_start_timer)/1000.0;
1224 float rate = fps_counter/secs;
1226 if (secs > 10000000) {
1227 // the timers are unsigned, so when the clock runs "backwards" it leads
1228 // to a very high difference value. In theory, this should never happen
1229 // with ticks, but it does on my machine (which may have a hw problem?).
1230 std::cerr << "Time glitch detected, need to restart FPS counters, sorry..." << std::endl;
1232 fps_timer = current_timer;
1233 fps_start_timer = current_timer;
1234 fps_counter_total = 0;
1235 fps_counter = 0;
1236 return;
1239 // first FPS message?
1240 if (fps_timer == fps_start_timer) { // they're ints, so we can compare
1241 fps_rate_min = rate;
1242 fps_rate_max = rate;
1243 } else {
1244 fps_rate_min = std::min<float>(fps_rate_min, rate);
1245 fps_rate_max = std::max<float>(fps_rate_max, rate);
1248 float avg = fps_counter_total / secs_total;
1250 //log_debug("Effective frame rate: %0.2f fps", (float)(fps_counter/secs));
1251 std::cerr << boost::format("Effective frame rate: %0.2f fps "
1252 "(min %0.2f, avg %0.2f, max %0.2f, "
1253 "%u frames in %0.1f secs total, "
1254 "dropped %u)") % rate %
1255 fps_rate_min % avg % fps_rate_max %
1256 fps_counter_total % secs_total %
1257 frames_dropped << std::endl;
1259 fps_counter = 0;
1260 fps_timer = current_timer;
1265 #endif
1267 void
1268 Gui::addFlashVars(Gui::VariableMap& from)
1270 for (VariableMap::iterator i=from.begin(), ie=from.end(); i!=ie; ++i) {
1271 _flashVars[i->first] = i->second;
1275 void
1276 Gui::addScriptableVar(const std::string &name, const std::string &value)
1278 log_debug("Adding scriptable variable \"%s\" = %s",
1279 name, value);
1280 _scriptableVars[name] = value;
1283 void
1284 Gui::setMovieDefinition(movie_definition* md)
1286 assert(!_movieDef);
1287 _movieDef = md;
1290 void
1291 Gui::setStage(movie_root* stage)
1293 assert(stage);
1294 assert(!_stage);
1295 _stage = stage;
1298 bool
1299 Gui::yesno(const std::string& question)
1301 log_error("This gui didn't override 'yesno', assuming 'yes' answer to "
1302 "question: %s", question);
1303 return true;
1306 void
1307 Gui::setQuality(Quality q)
1309 if (!_stage) {
1310 log_error("Gui::setQuality called before a movie_root was available");
1311 return;
1313 _stage->setQuality(q);
1316 Quality
1317 Gui::getQuality() const
1319 if (!_stage) {
1320 log_error("Gui::getQuality called before a movie_root was available");
1321 // just a guess..
1322 return QUALITY_HIGH;
1324 return _stage->getQuality();
1327 void
1328 Gui::setFDCallback(int fd, boost::function<void ()> callback)
1330 log_debug("Setting callback for fd #%d", fd);
1332 _fd_callbacks[fd] = callback;
1334 watchFD(fd);
1338 void
1339 Gui::callCallback(int fd)
1341 std::map<int, boost::function<void ()> >::iterator it = _fd_callbacks.find(fd);
1343 if (it == _fd_callbacks.end()) {
1344 log_error("Attempted to call a callback for an unregistered fd.");
1345 return;
1348 boost::function<void()>& f = it->second;
1350 f();
1353 void
1354 ScreenShotter::saveImage(const std::string& id) const
1356 // Replace all "%f" in the filename with the frameAdvance.
1357 std::string outfile(_fileName);
1358 boost::replace_all(outfile, "%f", id);
1360 FILE* f = std::fopen(outfile.c_str(), "wb");
1361 if (f) {
1362 boost::shared_ptr<IOChannel> t(new tu_file(f, true));
1363 _renderer->renderToImage(t, GNASH_FILETYPE_PNG);
1364 } else {
1365 log_error("Failed to open screenshot file \"%s\"!", outfile);
1369 void
1370 ScreenShotter::screenShot(size_t frameAdvance)
1372 // Save an image if an spontaneous screenshot was requested or the
1373 // frame is in the list of requested frames.
1374 if (_immediate || std::binary_search(_frames.begin(), _frames.end(),
1375 frameAdvance)) {
1376 saveImage(boost::lexical_cast<std::string>(frameAdvance));
1377 _immediate = false;
1381 void
1382 ScreenShotter::last() const
1384 if (_last) {
1385 saveImage("last");
1389 void
1390 ScreenShotter::setFrames(const FrameList& frames)
1392 _frames = frames;
1393 std::sort(_frames.begin(), _frames.end());
1396 // end of namespace
1399 // local Variables:
1400 // mode: C++
1401 // indent-tabs-mode: nil
1402 // End: