Merge branch 'master' of git://git.sv.gnu.org/gnash
[gnash.git] / gui / gui.cpp
blob08119be0444ac164509fb5496651c6915bbcc914
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 "MovieClip.h"
26 #include "gui.h"
27 #include "Renderer.h"
28 #include "sound_handler.h"
29 #include "movie_root.h"
30 #include "VM.h"
31 #include "DisplayObject.h"
32 #include "tu_file.h"
33 #include "gnash.h"
34 #include "RunResources.h"
36 #ifdef GNASH_FPS_DEBUG
37 #include "ClockTime.h"
38 #include <boost/format.hpp>
39 #endif
41 #include <vector>
42 #include <cstdio>
43 #include <cstring>
44 #include <algorithm>
45 #include <boost/algorithm/string/replace.hpp>
46 #include <boost/lexical_cast.hpp>
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
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
62 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
63 // a runtime check would make the { x; } block conditionally executed
64 #define IF_DEBUG_REGION_UPDATES(x) { if (_showUpdatedRegions) { x } }
65 #else
66 #define IF_DEBUG_REGION_UPDATES(x)
67 #endif
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.
73 //#define DEBUG_MOUSE_COORDINATES 1
75 // Define the following macro if you want to skip rendering
76 // when late on FPS time.
77 // This is an experimental feature, so it's off by default
78 //#define SKIP_RENDERING_IF_LATE 1
81 namespace gnash {
83 Gui::Gui(RunResources& r) :
84 _loop(true),
85 _xid(0),
86 _width(1),
87 _height(1),
88 _runResources(r),
89 _interval(0),
90 _redraw_flag(true),
91 _fullscreen(false),
92 _mouseShown(true),
93 _maxAdvances(0),
94 _advances(0),
95 _xscale(1.0f),
96 _yscale(1.0f),
97 _xoffset(0),
98 _yoffset(0)
99 #ifdef GNASH_FPS_DEBUG
100 ,fps_counter(0)
101 ,fps_counter_total(0)
102 ,fps_timer(0)
103 ,fps_timer_interval(0.0)
104 ,frames_dropped(0)
105 #endif
106 ,_movieDef(0)
107 ,_stage(0)
108 ,_stopped(false)
109 ,_started(false)
110 ,_showUpdatedRegions(false)
112 // NOTE: it's important that _systemClock is constructed
113 // before and destroyed after _virtualClock !
114 ,_systemClock()
115 ,_virtualClock(_systemClock)
116 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
117 ,_xpointer(0)
118 ,_ypointer(0)
119 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
120 ,_keyboardMouseMovementsStep(1)
121 #endif
126 Gui::Gui(unsigned long xid, float scale, bool loop, RunResources& r)
128 _loop(loop),
129 _xid(xid),
130 _width(1),
131 _height(1),
132 _runResources(r),
133 _interval(0),
134 _redraw_flag(true),
135 _fullscreen(false),
136 _mouseShown(true),
137 _maxAdvances(0),
138 _advances(0),
139 _xscale(scale),
140 _yscale(scale),
141 _xoffset(0), // TODO: x and y offset will need update !
142 _yoffset(0)
143 #ifdef GNASH_FPS_DEBUG
144 ,fps_counter(0)
145 ,fps_counter_total(0)
146 ,fps_timer(0)
147 ,fps_timer_interval(0.0)
148 ,frames_dropped(0)
149 #endif
150 ,_movieDef(0)
151 ,_stage(0)
152 ,_stopped(false)
153 ,_started(false)
154 ,_showUpdatedRegions(false)
156 // NOTE: it's important that _systemClock is constructed
157 // before and destroyed after _virtualClock !
158 ,_systemClock()
159 ,_virtualClock(_systemClock)
160 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
161 ,_xpointer(0)
162 ,_ypointer(0)
163 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
164 ,_keyboardMouseMovementsStep(1)
165 #endif
169 Gui::~Gui()
171 if ( _movieDef.get() ) log_debug("~Gui - _movieDef refcount: %d", _movieDef->get_ref_count());
173 #ifdef GNASH_FPS_DEBUG
174 if ( fps_timer_interval ) {
175 std::cerr << "Total frame advances/drops: "
176 << fps_counter_total << "/" << frames_dropped << std::endl;
178 #endif
181 void
182 Gui::setFullscreen()
184 log_unimpl(_("Fullscreen not yet supported in this GUI"));
187 void
188 Gui::resizeWindow(int /*width*/, int /*height*/)
190 log_unimpl(_("Window resize not yet supported in this GUI"));
193 void
194 Gui::unsetFullscreen()
196 log_unimpl(_("Fullscreen not yet supported in this GUI"));
199 void
200 Gui::quit()
202 log_debug(__PRETTY_FUNCTION__);
204 // Take a screenshot of the last frame if required.
205 if (_screenShotter.get()) {
206 _screenShotter->last();
209 quitUI();
212 void
213 Gui::hideMenu()
215 log_unimpl(_("Menu hiding not yet supported in this GUI"));
218 bool
219 Gui::showMouse(bool /* show */)
221 LOG_ONCE(log_unimpl(_("Mouse show/hide not yet supported in this GUI")));
222 return true;
225 void
226 Gui::showMenu(bool /* show */)
228 LOG_ONCE(log_unimpl(_("menushow not yet supported in this GUI")));
231 void
232 Gui::allowScale(bool allow)
234 if (!_stage) {
235 log_error("Gui::allowScale called before a movie_root was available");
236 return;
239 if (allow) _stage->setStageScaleMode(movie_root::SCALEMODE_SHOWALL);
240 else _stage->setStageScaleMode(movie_root::SCALEMODE_NOSCALE);
243 void
244 Gui::toggleFullscreen()
246 /// Sends request to Gnash core to change display state.
247 if (_fullscreen) {
248 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
250 else {
251 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_FULLSCREEN);
255 void
256 Gui::restart()
258 _stage->reset();
259 _started = false;
260 start();
263 void
264 Gui::updateStageMatrix()
266 if ( ! VM::isInitialized() ) {
267 // When VM initializes, we'll get a call to resize_view, which
268 // would call us again.
269 //log_debug("Can't update stage matrix till VM is initialized");
270 return;
273 assert(_stage); // when VM is initialized this should hold
275 float swfwidth = _movieDef->get_width_pixels();
276 float swfheight = _movieDef->get_height_pixels();
278 // Fetch scale mode
279 movie_root::ScaleMode scaleMode = _stage->getStageScaleMode();
281 switch (scaleMode) {
282 case movie_root::SCALEMODE_NOSCALE:
283 _xscale = _yscale = 1.0f;
284 break;
286 case movie_root::SCALEMODE_SHOWALL:
287 // set new scale value ( user-pixel / pseudo-pixel ). Do
288 // not divide by zero, or we end up with an invalid
289 // stage matrix that returns nan values.
290 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
291 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
293 // Scale proportionally, using smallest scale
294 if (_xscale < _yscale) {
295 _yscale = _xscale;
297 else if (_yscale < _xscale) {
298 _xscale = _yscale;
300 break;
302 case movie_root::SCALEMODE_NOBORDER:
303 // set new scale value ( user-pixel / pseudo-pixel )
304 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
305 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
307 // Scale proportionally, using biggest scale
308 if (_xscale > _yscale) {
309 _yscale = _xscale;
311 else if (_yscale > _xscale) {
312 _xscale = _yscale;
314 break;
316 case movie_root::SCALEMODE_EXACTFIT:
317 // NOTE: changing aspect ratio is valid!
318 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
319 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
320 break;
322 default:
323 log_error("Invalid scaleMode %d", scaleMode);
324 break;
327 _xoffset=0;
328 _yoffset=0;
330 // Fetch align mode
331 movie_root::StageAlign align = _stage->getStageAlignment();
332 movie_root::StageHorizontalAlign halign = align.first;
333 movie_root::StageVerticalAlign valign = align.second;
335 // Handle horizontal alignment
336 switch ( halign ) {
337 case movie_root::STAGE_H_ALIGN_L:
339 // _xoffset=0 is fine
340 break;
343 case movie_root::STAGE_H_ALIGN_R:
345 // Offsets in pixels
346 float defWidth = swfwidth *= _xscale;
347 float diffWidth = _width-defWidth;
348 _xoffset = diffWidth;
349 break;
352 case movie_root::STAGE_V_ALIGN_C:
354 // Offsets in pixels
355 float defWidth = swfwidth *= _xscale;
356 float diffWidth = _width-defWidth;
357 _xoffset = diffWidth/2.0;
358 break;
361 default:
363 log_error("Invalid horizontal align %d", valign);
364 break;
368 // Handle vertical alignment
369 switch ( valign ) {
370 case movie_root::STAGE_V_ALIGN_T:
372 // _yoffset=0 is fine
373 break;
376 case movie_root::STAGE_V_ALIGN_B:
378 float defHeight = swfheight *= _yscale;
379 float diffHeight = _height-defHeight;
380 _yoffset = diffHeight;
381 break;
384 case movie_root::STAGE_V_ALIGN_C:
386 float defHeight = swfheight *= _yscale;
387 float diffHeight = _height-defHeight;
388 _yoffset = diffHeight/2.0;
389 break;
392 default:
394 log_error("Invalid vertical align %d", valign);
395 break;
399 //log_debug("updateStageMatrix: scaleMode:%d, valign:%d, halign:%d",
400 //scaleMode, valign, halign);
402 // TODO: have a generic set_matrix ?
403 if (_renderer.get()) {
404 _renderer->set_scale(_xscale, _yscale);
405 _renderer->set_translation(_xoffset, _yoffset);
406 } else {
407 //log_debug("updateStageMatrix: could not signal updated stage
408 //matrix to renderer (no renderer registered)");
411 // trigger redraw
412 //_redraw_flag |= (_width!=width) || (_height!=height);
413 _redraw_flag = true; // this fixes bug #21971
417 void
418 Gui::resize_view(int width, int height)
421 assert(width>0);
422 assert(height>0);
424 if ( VM::isInitialized() )
427 if (_stage && _started) {
428 _stage->setDimensions(width, height);
433 _width = width;
434 _height = height;
435 _validbounds.setTo(0, 0, _width, _height);
437 updateStageMatrix();
439 if ( _stage && _started ) display(_stage);
443 void
444 Gui::toggleSound()
446 assert (_stage);
447 // @todo since we registered the sound handler, shouldn't we know
448 // already what it is ?!
449 sound::sound_handler* s = _stage->runResources().soundHandler();
451 if (!s) return;
453 if (s->is_muted()) s->unmute();
454 else s->mute();
458 void
459 Gui::notifyMouseMove(int ux, int uy)
461 movie_root* m = _stage;
463 if ( ! _started ) return;
465 if ( _stopped ) return;
467 // A stage pseudopixel is user pixel / _xscale wide
468 boost::int32_t x = (ux-_xoffset) / _xscale;
470 // A stage pseudopixel is user pixel / _xscale high
471 boost::int32_t y = (uy-_yoffset) / _yscale;
473 #ifdef DEBUG_MOUSE_COORDINATES
474 log_debug(_("mouse @ %d,%d"), x, y);
475 #endif
477 if ( m->mouseMoved(x, y) )
479 // any action triggered by the
480 // event required screen refresh
481 display(m);
484 DisplayObject* activeEntity = m->getActiveEntityUnderPointer();
485 if ( activeEntity )
487 if ( activeEntity->isSelectableTextField() )
489 setCursor(CURSOR_INPUT);
491 else if ( activeEntity->allowHandCursor() )
493 setCursor(CURSOR_HAND);
495 else
497 setCursor(CURSOR_NORMAL);
500 else
502 setCursor(CURSOR_NORMAL);
505 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
506 _xpointer = ux;
507 _ypointer = uy;
508 #endif
513 void
514 Gui::notifyMouseWheel(int delta)
516 movie_root* m = _stage;
517 assert(m);
519 if (!_started) return;
520 if (_stopped) return;
522 if (m->mouseWheel(delta)) {
523 // any action triggered by the
524 // event required screen refresh
525 display(m);
529 void
530 Gui::notifyMouseClick(bool mouse_pressed)
532 movie_root* m = _stage;
533 assert(m);
535 if (!_started) return;
536 if (_stopped) return;
538 if (m->mouseClick(mouse_pressed)) {
539 // any action triggered by the
540 // event required screen refresh
541 display(m);
545 void
546 Gui::refreshView()
548 movie_root* m = _stage;
550 if ( ! _started ) return;
552 assert(m);
553 _redraw_flag=true;
554 display(m);
558 void
559 Gui::notify_key_event(gnash::key::code k, int modifier, bool pressed)
562 /* Handle GUI shortcuts */
563 if (pressed) {
564 if (k == gnash::key::ESCAPE) {
565 if (isFullscreen()) {
566 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
570 if (modifier & gnash::key::GNASH_MOD_CONTROL) {
571 switch (k) {
572 case gnash::key::o:
573 case gnash::key::O:
574 takeScreenShot();
575 break;
576 case gnash::key::r:
577 case gnash::key::R:
578 restart();
579 break;
580 case gnash::key::p:
581 case gnash::key::P:
582 pause();
583 break;
584 case gnash::key::l:
585 case gnash::key::L:
586 refreshView();
587 break;
588 case gnash::key::q:
589 case gnash::key::Q:
590 case gnash::key::w:
591 case gnash::key::W:
592 quit();
593 break;
594 case gnash::key::f:
595 case gnash::key::F:
596 toggleFullscreen();
597 break;
598 case gnash::key::h:
599 case gnash::key::H:
600 showUpdatedRegions(!showUpdatedRegions());
601 break;
602 case gnash::key::MINUS:
604 // Max interval allowed: 1 second (1FPS)
605 const size_t ni = std::min<size_t>(_interval + 2, 1000u);
606 setInterval(ni);
607 break;
609 case gnash::key::PLUS:
611 // Min interval allowed: 1/100 second (100FPS)
612 const size_t ni = std::max<size_t>(_interval - 2, 10u);
613 setInterval(ni);
614 break;
616 case gnash::key::EQUALS:
618 if (_stage) {
619 const float fps = _stage->frameRate();
620 // Min interval allowed: 1/100 second (100FPS)
621 const size_t ni = 1000.0/fps;
622 setInterval(ni);
624 break;
626 default:
627 break;
630 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
631 if ( _keyboardMouseMovements ) {
632 int step = _keyboardMouseMovementsStep;
633 // x5 if SHIFT is pressed
634 if (modifier & gnash::key::GNASH_MOD_SHIFT) step *= 5;
635 switch (k) {
636 case gnash::key::UP:
638 int newx = _xpointer;
639 int newy = _ypointer-step;
640 if ( newy < 0 ) newy=0;
641 notifyMouseMove(newx, newy);
642 break;
644 case gnash::key::DOWN:
646 int newx = _xpointer;
647 int newy = _ypointer+step;
648 if ( newy >= _height ) newy = _height-1;
649 notifyMouseMove(newx, newy);
650 break;
652 case gnash::key::LEFT:
654 int newx = _xpointer-step;
655 int newy = _ypointer;
656 if ( newx < 0 ) newx = 0;
657 notifyMouseMove(newx, newy);
658 break;
660 case gnash::key::RIGHT:
662 const int newy = _ypointer;
663 int newx = _xpointer + step;
664 if ( newx >= _width ) newx = _width-1;
665 notifyMouseMove(newx, newy);
666 break;
668 default:
669 break;
672 #endif // ENABLE_KEYBOARD_MOUSE_MOVEMENTS
676 if (!_started) return;
678 if (_stopped) return;
680 if (_stage->keyEvent(k, pressed)) {
681 // any action triggered by the
682 // event required screen refresh
683 display(_stage);
688 bool
689 Gui::display(movie_root* m)
691 assert(m == _stage); // why taking this arg ??
693 assert(_started);
695 InvalidatedRanges changed_ranges;
696 bool redraw_flag;
698 // Should the frame be rendered completely, even if it did not change?
699 #ifdef FORCE_REDRAW
700 redraw_flag = true;
701 #else
702 redraw_flag = _redraw_flag || want_redraw();
703 #endif
705 // reset class member if we do a redraw now
706 if (redraw_flag) _redraw_flag=false;
708 // Find out the surrounding frame of all characters which
709 // have been updated. This just checks what region of the stage has changed
710 // due to ActionScript code, the timeline or user events. The GUI can still
711 // choose to render a different part of the stage.
713 if (!redraw_flag) {
715 // choose snapping ranges factor
716 changed_ranges.setSnapFactor(1.3f);
718 // Use multi ranges only when GUI/Renderer supports it
719 // (Useless CPU overhead, otherwise)
720 changed_ranges.setSingleMode(!want_multiple_regions());
722 // scan through all sprites to compute invalidated bounds
723 m->add_invalidated_bounds(changed_ranges, false);
725 // grow ranges by a 2 pixels to avoid anti-aliasing issues
726 changed_ranges.growBy(40.0f / _xscale);
728 // optimize ranges
729 changed_ranges.combineRanges();
733 // TODO: Remove this and want_redraw to avoid confusion!?
734 if (redraw_flag) {
735 changed_ranges.setWorld();
738 // DEBUG ONLY:
739 // This is a good place to inspect the invalidated bounds state. Enable
740 // the following block (and parts of it) if you need to.
741 #if 0
743 // This may print a huge amount of information, but is useful to analyze
744 // the (visible) object structure of the movie and the flags of the
745 // characters. For example, a characters should have set the
746 // m_child_invalidated flag if at least one of it's childs has the
747 // invalidated flag set.
748 log_debug("DUMPING CHARACTER TREE");
750 InfoTree tr;
751 InfoTree::iterator top = tr.begin();
752 _stage->getMovieInfo(tr, top);
754 for (InfoTree::iterator i = tr.begin(), e = tr.end();
755 i != e; ++i) {
756 std::cout << std::string(tr.depth(i) * 2, ' ') << i->first << ": " <<
757 i->second << std::endl;
761 // less verbose, and often necessary: see the exact coordinates of the
762 // invalidated bounds (mainly to see if it's NULL or something else).
763 std::cout << "Calculated changed ranges: " << changed_ranges << "\n";
765 #endif
767 // Avoid drawing of stopped movies
768 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
788 // TODO: should this be called even if we're late ?
789 beforeRendering();
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();
796 // show invalidated region using a red rectangle
797 // (Flash debug style)
798 IF_DEBUG_REGION_UPDATES (
799 if (_renderer.get() && !changed_ranges.isWorld())
802 for (size_t rno = 0; rno < changed_ranges.size(); rno++) {
804 const geometry::Range2d<int>& bounds =
805 changed_ranges.getRange(rno);
807 point corners[4];
808 float xmin = bounds.getMinX();
809 float xmax = bounds.getMaxX();
810 float ymin = bounds.getMinY();
811 float ymax = bounds.getMaxY();
813 corners[0].x = xmin;
814 corners[0].y = ymin;
815 corners[1].x = xmax;
816 corners[1].y = ymin;
817 corners[2].x = xmax;
818 corners[2].y = ymax;
819 corners[3].x = xmin;
820 corners[3].y = ymax;
821 SWFMatrix no_transform;
822 _renderer->draw_poly(corners, 4,
823 rgba(0,0,0,0), rgba(255,0,0,255), no_transform, false);
829 // show frame on screen
830 renderBuffer();
834 return true;
837 void
838 Gui::play()
840 if ( ! _stopped ) return;
842 _stopped = false;
843 if ( ! _started ) start();
844 else
846 assert (_stage);
847 // @todo since we registered the sound handler, shouldn't we know
848 // already what it is ?!
849 sound::sound_handler* s = _stage->runResources().soundHandler();
850 if ( s ) s->unpause();
852 log_debug("Starting virtual clock");
853 _virtualClock.resume();
856 playHook ();
859 void
860 Gui::stop()
862 // _stage must be registered before this is called.
863 assert(_stage);
865 if ( _stopped ) return;
866 if ( isFullscreen() ) unsetFullscreen();
868 _stopped = true;
870 // @todo since we registered the sound handler, shouldn't we know
871 // already what it is ?!
872 sound::sound_handler* s = _stage->runResources().soundHandler();
873 if ( s ) s->pause();
875 log_debug("Pausing virtual clock");
876 _virtualClock.pause();
878 stopHook();
881 void
882 Gui::pause()
884 if (_stopped) {
885 play();
886 return;
889 // TODO: call stop() instead ?
890 // The only thing I see is that ::stop exits full-screen,
891 // but I'm not sure that's intended behaviour
893 // @todo since we registered the sound handler, shouldn't we know
894 // already what it is ?!
895 sound::sound_handler* s = _stage->runResources().soundHandler();
896 if (s) s->pause();
897 _stopped = true;
899 log_debug("Pausing virtual clock");
900 _virtualClock.pause();
902 stopHook();
905 void
906 Gui::start()
908 assert ( ! _started );
909 if (_stopped) {
910 log_debug("Gui is in stop mode, won't start application");
911 return;
914 // Initializes the stage with a Movie and the passed flash vars and
915 // Scriptable vars for ExternalInterface.
916 _stage->init(_movieDef.get(), _flashVars, _scriptableVars);
918 bool background = true; // ??
919 _stage->set_background_alpha(background ? 1.0f : 0.05f);
921 // @todo since we registered the sound handler, shouldn't we know
922 // already what it is ?!
923 sound::sound_handler* s = _stage->runResources().soundHandler();
924 if ( s ) s->unpause();
925 _started = true;
927 // to properly update stageMatrix if scaling is given
928 resize_view(_width, _height);
930 log_debug("Starting virtual clock");
931 _virtualClock.resume();
935 bool
936 Gui::advanceMovie()
939 if (isStopped()) {
940 return true;
943 if (!_started) {
944 start();
947 gnash::movie_root* m = _stage;
949 // Define REVIEW_ALL_FRAMES to have *all* frames
950 // consequentially displayed. Useful for debugging.
951 //#define REVIEW_ALL_FRAMES 1
953 #ifndef REVIEW_ALL_FRAMES
954 // Advance movie by one frame
955 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();
961 m->getRootMovie.ensureFrameLoaded(tot_frames);
962 m->goto_frame(cur_frame + 1);
963 m->set_play_state(gnash::MovieClip::PLAYSTATE_PLAY);
964 log_debug(_("Frame %d"), m->get_current_frame());
965 #endif
967 #ifdef GNASH_FPS_DEBUG
968 // will be a no-op if fps_timer_interval is zero
969 if (advanced) {
970 fpsCounterTick();
972 #endif
975 // TODO: ask stage about doDisplay ?
976 // - if it didn't advance might need to check updateAfterEvent
977 bool doDisplay = true;
979 #ifdef SKIP_RENDERING_IF_LATE
980 // We want to skip rendering IFF it's time to advance again.
981 // We'll ask the stage about it
982 if (_stage->timeToNextFrame() <= 0) {
984 // or should it be if advanced ?
985 if (doDisplay) {
986 // TODO: take note of a frame drop (count them)
987 //log_debug("Frame rendering dropped due to being late");
988 #ifdef GNASH_FPS_DEBUG
989 ++frames_dropped;
990 #endif
992 doDisplay = false;
994 #endif // ndef SKIP_RENDERING_IF_LATE
996 if (doDisplay) display(m);
998 if (!loops()) {
999 size_t curframe = m->get_current_frame(); // can be 0 on malformed SWF
1000 const gnash::MovieClip& si = m->getRootMovie();
1001 if (curframe + 1 >= si.get_frame_count()) {
1002 quit();
1006 if (_screenShotter.get()) {
1007 _screenShotter->screenShot(_advances);
1010 // Only increment advances and check for exit condition when we've
1011 // really changed frame.
1012 if (advanced) {
1013 /// Quit if we've reached the frame advance limit.
1014 if (_maxAdvances && (_advances > _maxAdvances)) {
1015 quit();
1017 ++_advances;
1020 return true;
1023 void
1024 Gui::takeScreenShot()
1026 if (!_screenShotter.get()) {
1027 // If no ScreenShotter exists, none was requested at startup.
1028 // We use a default filename pattern.
1029 URL url(_runResources.baseURL());
1030 std::string::size_type p = url.path().rfind('/');
1031 const std::string& name = (p == std::string::npos) ? url.path() :
1032 url.path().substr(p + 1);
1033 const std::string& filename = "screenshot-" + name + "-%f";
1034 _screenShotter.reset(new ScreenShotter(_renderer, filename));
1036 assert (_screenShotter.get());
1037 _screenShotter->now();
1040 void
1041 Gui::requestScreenShots(const ScreenShotter::FrameList& l, bool last,
1042 const std::string& filename)
1044 // Nothing to do if there is no renderer or if no frames should be
1045 // saved.
1046 if (!_renderer.get() || (l.empty() && !last)) {
1047 return;
1050 _screenShotter.reset(new ScreenShotter(_renderer, filename));
1051 if (last) _screenShotter->lastFrame();
1052 _screenShotter->setFrames(l);
1056 void
1057 Gui::setCursor(gnash_cursor_type /*newcursor*/)
1059 /* do nothing */
1062 bool
1063 Gui::want_redraw()
1065 return false;
1068 void
1069 Gui::setInvalidatedRegion(const SWFRect& /*bounds*/)
1071 /* do nothing */
1074 void
1075 Gui::setInvalidatedRegions(const InvalidatedRanges& ranges)
1077 // fallback to single regions
1078 geometry::Range2d<int> full = ranges.getFullArea();
1080 SWFRect bounds;
1082 if (full.isFinite()) {
1083 bounds = SWFRect(full.getMinX(), full.getMinY(),
1084 full.getMaxX(), full.getMaxY());
1086 else if (full.isWorld()) {
1087 bounds.set_world();
1090 setInvalidatedRegion(bounds);
1093 #ifdef USE_SWFTREE
1095 std::auto_ptr<Gui::InfoTree>
1096 Gui::getMovieInfo() const
1098 std::auto_ptr<InfoTree> tr;
1100 if (! VM::isInitialized()) {
1101 return tr;
1104 tr.reset(new InfoTree());
1106 // Top nodes for the tree:
1107 // 1. VM information
1108 // 2. "Stage" information
1109 // 3. ...
1111 InfoTree::iterator topIter = tr->begin();
1112 InfoTree::iterator firstLevelIter;
1114 VM& vm = _stage->getVM();
1116 std::ostringstream os;
1119 /// VM top level
1121 os << "SWF " << vm.getSWFVersion();
1122 topIter = tr->insert(topIter, StringPair("VM version", os.str()));
1124 // This short-cut is to avoid a bug in movie_root's getMovieInfo,
1125 // which relies on the availability of a _rootMovie for doing
1126 // it's work, while we don't set it if we didn't start..
1128 if (! _started) {
1129 topIter = tr->insert(topIter, StringPair("Stage properties",
1130 "not constructed yet"));
1131 return tr;
1134 movie_root& stage = vm.getRoot();
1135 stage.getMovieInfo(*tr, topIter);
1138 /// Mouse entities
1140 topIter = tr->insert(topIter, StringPair("Mouse Entities", ""));
1142 const DisplayObject* ch;
1143 ch = stage.getActiveEntityUnderPointer();
1144 if (ch) {
1145 std::stringstream ss;
1146 ss << ch->getTarget() << " (" + typeName(*ch)
1147 << " - depth:" << ch->get_depth()
1148 << " - useHandCursor:" << ch->allowHandCursor()
1149 << ")";
1150 firstLevelIter = tr->append_child(topIter, StringPair("Active entity under mouse pointer", ss.str()));
1153 ch = stage.getEntityUnderPointer();
1154 if (ch) {
1155 std::stringstream ss;
1156 ss << ch->getTarget() << " (" + typeName(*ch)
1157 << " - depth:" << ch->get_depth()
1158 << ")";
1159 firstLevelIter = tr->append_child(topIter, StringPair("Topmost entity under mouse pointer", ss.str()));
1162 ch = stage.getDraggingCharacter();
1163 if (ch) {
1164 std::stringstream ss;
1165 ss << ch->getTarget() << " (" + typeName(*ch)
1166 << " - depth:" << ch->get_depth() << ")";
1167 firstLevelIter = tr->append_child(topIter, StringPair("Dragging character: ", ss.str()));
1171 /// GC row
1173 topIter = tr->insert(topIter, StringPair("GC Statistics", ""));
1174 GC::CollectablesCount cc;
1175 GC::get().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 StringPair(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: