reduce verbosity
[gnash.git] / gui / gui.cpp
blob5d924210a8d87e1c2044704b15fd842b856cad6d
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 <algorithm>
29 #include <boost/assign/list_of.hpp>
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"
42 #ifdef GNASH_FPS_DEBUG
43 #include "ClockTime.h"
44 #include <boost/format.hpp>
45 #endif
47 /// Define this to make sure each frame is fully rendered from ground up
48 /// even if no motion has been detected in the movie.
49 //#define FORCE_REDRAW 1
51 /// Define this if you want to debug the *detection* of region updates only.
52 /// This will disable region updates for the backend (GUI+renderer) completely
53 /// so that only the last region (red frame) will be visible. However, this
54 /// slows down rendering as each frame is fully re-rendered. If you want to
55 /// debug the GUI part, however (see if blitting the region works), then you
56 /// probably won't define this.
57 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
58 //#define REGION_UPDATES_DEBUGGING_FULL_REDRAW 1
59 #endif
61 #ifndef DISABLE_REGION_UPDATES_DEBUGGING
62 // a runtime check would make the { x; } block conditionally executed
63 #define IF_DEBUG_REGION_UPDATES(x) { if (_showUpdatedRegions) { x } }
64 #else
65 #define IF_DEBUG_REGION_UPDATES(x)
66 #endif
68 // Define this to have gnash print the mouse pointer coordinates
69 // as the mouse moves. See also ENABLE_KEYBOARD_MOUSE_MOVEMENTS
70 // to have more control over mouse pointer.
72 //#define DEBUG_MOUSE_COORDINATES 1
74 // Define the following macro if you want to skip rendering
75 // when late on FPS time.
76 // This is an experimental feature, so it's off by default
77 //#define SKIP_RENDERING_IF_LATE 1
80 namespace gnash {
82 struct Gui::Display
84 Display(Gui& g, movie_root& r) : _g(g), _r(r) {}
85 void operator()() const {
86 InvalidatedRanges world_ranges;
87 world_ranges.setWorld();
88 _g.setInvalidatedRegions(world_ranges);
89 _g.display(&_r);
91 private:
92 Gui& _g;
93 movie_root& _r;
96 Gui::Gui(RunResources& r) :
97 _loop(true),
98 _xid(0),
99 _width(1),
100 _height(1),
101 _runResources(r),
102 _interval(0),
103 _redraw_flag(true),
104 _fullscreen(false),
105 _mouseShown(true),
106 _maxAdvances(0),
107 _advances(0),
108 _xscale(1.0f),
109 _yscale(1.0f),
110 _xoffset(0),
111 _yoffset(0)
112 #ifdef GNASH_FPS_DEBUG
113 ,fps_counter(0)
114 ,fps_counter_total(0)
115 ,fps_timer(0)
116 ,fps_timer_interval(0.0)
117 ,frames_dropped(0)
118 #endif
119 ,_movieDef(0)
120 ,_stage(0)
121 ,_stopped(false)
122 ,_started(false)
123 ,_showUpdatedRegions(false)
125 // NOTE: it's important that _systemClock is constructed
126 // before and destroyed after _virtualClock !
127 ,_systemClock()
128 ,_virtualClock(_systemClock)
129 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
130 ,_xpointer(0)
131 ,_ypointer(0)
132 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
133 ,_keyboardMouseMovementsStep(1)
134 #endif
139 Gui::Gui(unsigned long xid, float scale, bool loop, RunResources& r)
141 _loop(loop),
142 _xid(xid),
143 _width(1),
144 _height(1),
145 _runResources(r),
146 _interval(0),
147 _redraw_flag(true),
148 _fullscreen(false),
149 _mouseShown(true),
150 _maxAdvances(0),
151 _advances(0),
152 _xscale(scale),
153 _yscale(scale),
154 _xoffset(0), // TODO: x and y offset will need update !
155 _yoffset(0)
156 #ifdef GNASH_FPS_DEBUG
157 ,fps_counter(0)
158 ,fps_counter_total(0)
159 ,fps_timer(0)
160 ,fps_timer_interval(0.0)
161 ,frames_dropped(0)
162 #endif
163 ,_movieDef(0)
164 ,_stage(0)
165 ,_stopped(false)
166 ,_started(false)
167 ,_showUpdatedRegions(false)
169 // NOTE: it's important that _systemClock is constructed
170 // before and destroyed after _virtualClock !
171 ,_systemClock()
172 ,_virtualClock(_systemClock)
173 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
174 ,_xpointer(0)
175 ,_ypointer(0)
176 ,_keyboardMouseMovements(true) // TODO: base default on gnashrc or always false and provide menu item to toggle
177 ,_keyboardMouseMovementsStep(1)
178 #endif
182 Gui::~Gui()
184 if ( _movieDef.get() ) log_debug("~Gui - _movieDef refcount: %d", _movieDef->get_ref_count());
186 #ifdef GNASH_FPS_DEBUG
187 if ( fps_timer_interval ) {
188 std::cerr << "Total frame advances/drops: "
189 << fps_counter_total << "/" << frames_dropped << std::endl;
191 #endif
194 void
195 Gui::setClipboard(const std::string&)
197 LOG_ONCE(log_unimpl(_("Clipboard not yet supported in this GUI")));
200 void
201 Gui::setFullscreen()
203 log_unimpl(_("Fullscreen not yet supported in this GUI"));
206 void
207 Gui::resizeWindow(int /*width*/, int /*height*/)
209 log_unimpl(_("Window resize not yet supported in this GUI"));
212 void
213 Gui::unsetFullscreen()
215 log_unimpl(_("Fullscreen not yet supported in this GUI"));
218 void
219 Gui::quit()
221 // Take a screenshot of the last frame if required.
222 if (_screenShotter.get() && _renderer.get()) {
223 Display dis(*this, *_stage);
224 _screenShotter->last(*_renderer, &dis);
227 quitUI();
230 void
231 Gui::hideMenu()
233 log_unimpl(_("Menu hiding not yet supported in this GUI"));
236 bool
237 Gui::showMouse(bool /* show */)
239 LOG_ONCE(log_unimpl(_("Mouse show/hide not yet supported in this GUI")));
240 return true;
243 void
244 Gui::showMenu(bool /* show */)
246 LOG_ONCE(log_unimpl(_("menushow not yet supported in this GUI")));
249 void
250 Gui::allowScale(bool allow)
252 if (!_stage) {
253 log_error("Gui::allowScale called before a movie_root was available");
254 return;
257 if (allow) _stage->setStageScaleMode(movie_root::SCALEMODE_SHOWALL);
258 else _stage->setStageScaleMode(movie_root::SCALEMODE_NOSCALE);
261 void
262 Gui::toggleFullscreen()
264 /// Sends request to Gnash core to change display state.
265 if (_fullscreen) {
266 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
268 else {
269 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_FULLSCREEN);
273 void
274 Gui::restart()
276 _stage->reset();
277 _started = false;
278 start();
281 void
282 Gui::updateStageMatrix()
284 if (!_stage) {
285 // When VM initializes, we'll get a call to resize_view, which
286 // would call us again.
287 //log_debug("Can't update stage matrix till VM is initialized");
288 return;
291 assert(_stage); // when VM is initialized this should hold
293 float swfwidth = _movieDef->get_width_pixels();
294 float swfheight = _movieDef->get_height_pixels();
296 // Fetch scale mode
297 movie_root::ScaleMode scaleMode = _stage->getStageScaleMode();
299 switch (scaleMode) {
300 case movie_root::SCALEMODE_NOSCALE:
301 _xscale = _yscale = 1.0f;
302 break;
304 case movie_root::SCALEMODE_SHOWALL:
305 // set new scale value ( user-pixel / pseudo-pixel ). Do
306 // not divide by zero, or we end up with an invalid
307 // stage matrix that returns nan values.
308 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
309 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
311 // Scale proportionally, using smallest scale
312 if (_xscale < _yscale) {
313 _yscale = _xscale;
315 else if (_yscale < _xscale) {
316 _xscale = _yscale;
318 break;
320 case movie_root::SCALEMODE_NOBORDER:
321 // set new scale value ( user-pixel / pseudo-pixel )
322 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
323 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
325 // Scale proportionally, using biggest scale
326 if (_xscale > _yscale) {
327 _yscale = _xscale;
329 else if (_yscale > _xscale) {
330 _xscale = _yscale;
332 break;
334 case movie_root::SCALEMODE_EXACTFIT:
335 // NOTE: changing aspect ratio is valid!
336 _xscale = (swfwidth == 0.0f) ? 1.0f : _width / swfwidth;
337 _yscale = (swfheight == 0.0f) ? 1.0f : _height / swfheight;
338 break;
340 default:
341 log_error("Invalid scaleMode %d", scaleMode);
342 break;
345 _xoffset=0;
346 _yoffset=0;
348 // Fetch align mode
349 movie_root::StageAlign align = _stage->getStageAlignment();
350 movie_root::StageHorizontalAlign halign = align.first;
351 movie_root::StageVerticalAlign valign = align.second;
353 // Handle horizontal alignment
354 switch ( halign ) {
355 case movie_root::STAGE_H_ALIGN_L:
357 // _xoffset=0 is fine
358 break;
361 case movie_root::STAGE_H_ALIGN_R:
363 // Offsets in pixels
364 float defWidth = swfwidth *= _xscale;
365 float diffWidth = _width-defWidth;
366 _xoffset = diffWidth;
367 break;
370 case movie_root::STAGE_V_ALIGN_C:
372 // Offsets in pixels
373 float defWidth = swfwidth *= _xscale;
374 float diffWidth = _width-defWidth;
375 _xoffset = diffWidth/2.0;
376 break;
379 default:
381 log_error("Invalid horizontal align %d", valign);
382 break;
386 // Handle vertical alignment
387 switch ( valign ) {
388 case movie_root::STAGE_V_ALIGN_T:
390 // _yoffset=0 is fine
391 break;
394 case movie_root::STAGE_V_ALIGN_B:
396 float defHeight = swfheight *= _yscale;
397 float diffHeight = _height-defHeight;
398 _yoffset = diffHeight;
399 break;
402 case movie_root::STAGE_V_ALIGN_C:
404 float defHeight = swfheight *= _yscale;
405 float diffHeight = _height-defHeight;
406 _yoffset = diffHeight/2.0;
407 break;
410 default:
412 log_error("Invalid vertical align %d", valign);
413 break;
417 //log_debug("updateStageMatrix: scaleMode:%d, valign:%d, halign:%d",
418 //scaleMode, valign, halign);
420 // TODO: have a generic set_matrix ?
421 if (_renderer.get()) {
422 _renderer->set_scale(_xscale, _yscale);
423 _renderer->set_translation(_xoffset, _yoffset);
424 } else {
425 //log_debug("updateStageMatrix: could not signal updated stage
426 //matrix to renderer (no renderer registered)");
429 // trigger redraw
430 //_redraw_flag |= (_width!=width) || (_height!=height);
431 _redraw_flag = true; // this fixes bug #21971
435 void
436 Gui::resize_view(int width, int height)
439 assert(width>0);
440 assert(height>0);
442 if (_stage && _started) {
443 _stage->setDimensions(width, height);
446 _width = width;
447 _height = height;
448 _validbounds.setTo(0, 0, _width, _height);
450 updateStageMatrix();
452 if ( _stage && _started ) display(_stage);
456 void
457 Gui::toggleSound()
459 assert (_stage);
460 // @todo since we registered the sound handler, shouldn't we know
461 // already what it is ?!
462 sound::sound_handler* s = _stage->runResources().soundHandler();
464 if (!s) return;
466 if (s->is_muted()) s->unmute();
467 else s->mute();
471 void
472 Gui::notifyMouseMove(int ux, int uy)
474 movie_root* m = _stage;
476 if ( ! _started ) return;
478 if ( _stopped ) return;
480 // A stage pseudopixel is user pixel / _xscale wide
481 boost::int32_t x = (ux-_xoffset) / _xscale;
483 // A stage pseudopixel is user pixel / _xscale high
484 boost::int32_t y = (uy-_yoffset) / _yscale;
486 #ifdef DEBUG_MOUSE_COORDINATES
487 log_debug(_("mouse @ %d,%d"), x, y);
488 #endif
490 if ( m->mouseMoved(x, y) )
492 // any action triggered by the
493 // event required screen refresh
494 display(m);
497 DisplayObject* activeEntity = m->getActiveEntityUnderPointer();
498 if ( activeEntity )
500 if ( activeEntity->isSelectableTextField() )
502 setCursor(CURSOR_INPUT);
504 else if ( activeEntity->allowHandCursor() )
506 setCursor(CURSOR_HAND);
508 else
510 setCursor(CURSOR_NORMAL);
513 else
515 setCursor(CURSOR_NORMAL);
518 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
519 _xpointer = ux;
520 _ypointer = uy;
521 #endif
526 void
527 Gui::notifyMouseWheel(int delta)
529 movie_root* m = _stage;
530 assert(m);
532 if (!_started) return;
533 if (_stopped) return;
535 if (m->mouseWheel(delta)) {
536 // any action triggered by the
537 // event required screen refresh
538 display(m);
542 void
543 Gui::notifyMouseClick(bool mouse_pressed)
545 movie_root* m = _stage;
546 assert(m);
548 if (!_started) return;
549 if (_stopped) return;
551 if (m->mouseClick(mouse_pressed)) {
552 // any action triggered by the
553 // event required screen refresh
554 display(m);
558 void
559 Gui::refreshView()
561 movie_root* m = _stage;
563 if ( ! _started ) return;
565 assert(m);
566 _redraw_flag=true;
567 display(m);
571 void
572 Gui::notify_key_event(gnash::key::code k, int modifier, bool pressed)
575 /* Handle GUI shortcuts */
576 if (pressed) {
577 if (k == gnash::key::ESCAPE) {
578 if (isFullscreen()) {
579 _stage->setStageDisplayState(movie_root::DISPLAYSTATE_NORMAL);
583 if (modifier & gnash::key::GNASH_MOD_CONTROL) {
584 switch (k) {
585 case gnash::key::o:
586 case gnash::key::O:
587 takeScreenShot();
588 break;
589 case gnash::key::r:
590 case gnash::key::R:
591 restart();
592 break;
593 case gnash::key::p:
594 case gnash::key::P:
595 pause();
596 break;
597 case gnash::key::l:
598 case gnash::key::L:
599 refreshView();
600 break;
601 case gnash::key::q:
602 case gnash::key::Q:
603 case gnash::key::w:
604 case gnash::key::W:
605 quit();
606 break;
607 case gnash::key::f:
608 case gnash::key::F:
609 toggleFullscreen();
610 break;
611 case gnash::key::h:
612 case gnash::key::H:
613 showUpdatedRegions(!showUpdatedRegions());
614 break;
615 case gnash::key::MINUS:
617 // Max interval allowed: 1 second (1FPS)
618 const size_t ni = std::min<size_t>(_interval + 2, 1000u);
619 setInterval(ni);
620 break;
622 case gnash::key::PLUS:
624 // Min interval allowed: 1/100 second (100FPS)
625 const size_t ni = std::max<size_t>(_interval - 2, 10u);
626 setInterval(ni);
627 break;
629 case gnash::key::EQUALS:
631 if (_stage) {
632 const float fps = _stage->frameRate();
633 // Min interval allowed: 1/100 second (100FPS)
634 const size_t ni = 1000.0/fps;
635 setInterval(ni);
637 break;
639 default:
640 break;
643 #ifdef ENABLE_KEYBOARD_MOUSE_MOVEMENTS
644 if ( _keyboardMouseMovements ) {
645 int step = _keyboardMouseMovementsStep;
646 // x5 if SHIFT is pressed
647 if (modifier & gnash::key::GNASH_MOD_SHIFT) step *= 5;
648 switch (k) {
649 case gnash::key::UP:
651 int newx = _xpointer;
652 int newy = _ypointer-step;
653 if ( newy < 0 ) newy=0;
654 notifyMouseMove(newx, newy);
655 break;
657 case gnash::key::DOWN:
659 int newx = _xpointer;
660 int newy = _ypointer+step;
661 if ( newy >= _height ) newy = _height-1;
662 notifyMouseMove(newx, newy);
663 break;
665 case gnash::key::LEFT:
667 int newx = _xpointer-step;
668 int newy = _ypointer;
669 if ( newx < 0 ) newx = 0;
670 notifyMouseMove(newx, newy);
671 break;
673 case gnash::key::RIGHT:
675 const int newy = _ypointer;
676 int newx = _xpointer + step;
677 if ( newx >= _width ) newx = _width-1;
678 notifyMouseMove(newx, newy);
679 break;
681 default:
682 break;
685 #endif // ENABLE_KEYBOARD_MOUSE_MOVEMENTS
689 if (!_started) return;
691 if (_stopped) return;
693 if (_stage->keyEvent(k, pressed)) {
694 // any action triggered by the
695 // event required screen refresh
696 display(_stage);
701 bool
702 Gui::display(movie_root* m)
704 assert(m == _stage); // why taking this arg ??
706 assert(_started);
708 InvalidatedRanges changed_ranges;
709 bool redraw_flag;
711 // Should the frame be rendered completely, even if it did not change?
712 #ifdef FORCE_REDRAW
713 redraw_flag = true;
714 #else
715 redraw_flag = _redraw_flag || want_redraw();
716 #endif
718 // reset class member if we do a redraw now
719 if (redraw_flag) _redraw_flag=false;
721 // Find out the surrounding frame of all characters which
722 // have been updated. This just checks what region of the stage has changed
723 // due to ActionScript code, the timeline or user events. The GUI can still
724 // choose to render a different part of the stage.
726 if (!redraw_flag) {
728 // choose snapping ranges factor
729 changed_ranges.setSnapFactor(1.3f);
731 // Use multi ranges only when GUI/Renderer supports it
732 // (Useless CPU overhead, otherwise)
733 changed_ranges.setSingleMode(!want_multiple_regions());
735 // scan through all sprites to compute invalidated bounds
736 m->add_invalidated_bounds(changed_ranges, false);
738 // grow ranges by a 2 pixels to avoid anti-aliasing issues
739 changed_ranges.growBy(40.0f / _xscale);
741 // optimize ranges
742 changed_ranges.combineRanges();
746 // TODO: Remove this and want_redraw to avoid confusion!?
747 if (redraw_flag) {
748 changed_ranges.setWorld();
751 // DEBUG ONLY:
752 // This is a good place to inspect the invalidated bounds state. Enable
753 // the following block (and parts of it) if you need to.
754 #if 0
756 // This may print a huge amount of information, but is useful to analyze
757 // the (visible) object structure of the movie and the flags of the
758 // characters. For example, a characters should have set the
759 // m_child_invalidated flag if at least one of it's childs has the
760 // invalidated flag set.
761 log_debug("DUMPING CHARACTER TREE");
763 InfoTree tr;
764 InfoTree::iterator top = tr.begin();
765 _stage->getMovieInfo(tr, top);
767 for (InfoTree::iterator i = tr.begin(), e = tr.end();
768 i != e; ++i) {
769 std::cout << std::string(tr.depth(i) * 2, ' ') << i->first << ": " <<
770 i->second << std::endl;
774 // less verbose, and often necessary: see the exact coordinates of the
775 // invalidated bounds (mainly to see if it's NULL or something else).
776 std::cout << "Calculated changed ranges: " << changed_ranges << "\n";
778 #endif
780 // Avoid drawing of stopped movies
781 if ( ! changed_ranges.isNull() ) // use 'else'?
783 // Tell the GUI(!) that we only need to update this
784 // region. Note the GUI can do whatever it wants with
785 // this information. It may simply ignore the bounds
786 // (which will normally lead into a complete redraw),
787 // or it may extend or shrink the bounds as it likes. So,
788 // by calling set_invalidated_bounds we have no guarantee
789 // that only this part of the stage is rendered again.
790 #ifdef REGION_UPDATES_DEBUGGING_FULL_REDRAW
791 // redraw the full screen so that only the
792 // *new* invalidated region is visible
793 // (helps debugging)
794 InvalidatedRanges world_ranges;
795 world_ranges.setWorld();
796 setInvalidatedRegions(world_ranges);
797 #else
798 setInvalidatedRegions(changed_ranges);
799 #endif
801 // TODO: should this be called even if we're late ?
802 beforeRendering();
804 // Render the frame, if not late.
805 // It's up to the GUI/renderer combination
806 // to do any clipping, if desired.
807 m->display();
809 // show invalidated region using a red rectangle
810 // (Flash debug style)
811 IF_DEBUG_REGION_UPDATES(
812 if (_renderer.get() && !changed_ranges.isWorld()) {
814 for (size_t rno = 0; rno < changed_ranges.size(); rno++) {
816 const geometry::Range2d<int>& bounds =
817 changed_ranges.getRange(rno);
819 float xmin = bounds.getMinX();
820 float xmax = bounds.getMaxX();
821 float ymin = bounds.getMinY();
822 float ymax = bounds.getMaxY();
824 const std::vector<point> box = boost::assign::list_of
825 (point(xmin, ymin))
826 (point(xmax, ymin))
827 (point(xmax, ymax))
828 (point(xmin, ymax));
830 _renderer->draw_poly(box, rgba(0,0,0,0), rgba(255,0,0,255),
831 SWFMatrix(), false);
837 // show frame on screen
838 renderBuffer();
842 return true;
845 void
846 Gui::play()
848 if ( ! _stopped ) return;
850 _stopped = false;
851 if ( ! _started ) start();
852 else
854 assert (_stage);
855 // @todo since we registered the sound handler, shouldn't we know
856 // already what it is ?!
857 sound::sound_handler* s = _stage->runResources().soundHandler();
858 if ( s ) s->unpause();
860 log_debug("Starting virtual clock");
861 _virtualClock.resume();
864 playHook ();
867 void
868 Gui::stop()
870 // _stage must be registered before this is called.
871 assert(_stage);
873 if ( _stopped ) return;
874 if ( isFullscreen() ) unsetFullscreen();
876 _stopped = true;
878 // @todo since we registered the sound handler, shouldn't we know
879 // already what it is ?!
880 sound::sound_handler* s = _stage->runResources().soundHandler();
881 if ( s ) s->pause();
883 log_debug("Pausing virtual clock");
884 _virtualClock.pause();
886 stopHook();
889 void
890 Gui::pause()
892 if (_stopped) {
893 play();
894 return;
897 // TODO: call stop() instead ?
898 // The only thing I see is that ::stop exits full-screen,
899 // but I'm not sure that's intended behaviour
901 // @todo since we registered the sound handler, shouldn't we know
902 // already what it is ?!
903 sound::sound_handler* s = _stage->runResources().soundHandler();
904 if (s) s->pause();
905 _stopped = true;
907 log_debug("Pausing virtual clock");
908 _virtualClock.pause();
910 stopHook();
913 void
914 Gui::start()
916 assert ( ! _started );
917 if (_stopped) {
918 log_debug("Gui is in stop mode, won't start application");
919 return;
922 // Initializes the stage with a Movie and the passed flash vars.
923 _stage->init(_movieDef.get(), _flashVars);
925 bool background = true; // ??
926 _stage->set_background_alpha(background ? 1.0f : 0.05f);
928 // to properly update stageMatrix if scaling is given
929 resize_view(_width, _height);
931 // @todo since we registered the sound handler, shouldn't we know
932 // already what it is ?!
933 sound::sound_handler* s = _stage->runResources().soundHandler();
934 if ( s ) {
935 if ( ! _audioDump.empty() ) {
936 s->setAudioDump(_audioDump);
938 s->unpause();
940 _started = true;
943 log_debug("Starting virtual clock");
944 _virtualClock.resume();
948 bool
949 Gui::advanceMovie(bool doDisplay)
953 if (isStopped()) {
954 return false;
957 if (!_started) {
958 start();
961 Display dis(*this, *_stage);
962 gnash::movie_root* m = _stage;
964 // Define REVIEW_ALL_FRAMES to have *all* frames
965 // consequentially displayed. Useful for debugging.
966 //#define REVIEW_ALL_FRAMES 1
968 #ifndef REVIEW_ALL_FRAMES
969 // Advance movie by one frame
970 const bool advanced = m->advance();
971 #else
972 const size_t cur_frame = m->getRootMovie()->get_current_frame();
973 const size_t tot_frames = m->getRootMovie()->get_frame_count();
974 const bool advanced = m->advance();
976 m->getRootMovie().ensureFrameLoaded(tot_frames);
977 m->goto_frame(cur_frame + 1);
978 m->getRootMovie().setPlayState(gnash::MovieClip::PLAYSTATE_PLAY);
979 log_debug(_("Frame %d"), m->get_current_frame());
980 #endif
982 #ifdef GNASH_FPS_DEBUG
983 // will be a no-op if fps_timer_interval is zero
984 if (advanced) {
985 fpsCounterTick();
987 #endif
989 #ifdef SKIP_RENDERING_IF_LATE
990 // We want to skip rendering IFF it's time to advance again.
991 // We'll ask the stage about it
992 if (_stage->timeToNextFrame() <= 0) {
994 // or should it be if advanced ?
995 if (doDisplay) {
996 // TODO: take note of a frame drop (count them)
997 //log_debug("Frame rendering dropped due to being late");
998 #ifdef GNASH_FPS_DEBUG
999 ++frames_dropped;
1000 #endif
1002 doDisplay = false;
1004 #endif // ndef SKIP_RENDERING_IF_LATE
1006 if (doDisplay) display(m);
1008 if (!loops()) {
1009 size_t curframe = m->get_current_frame(); // can be 0 on malformed SWF
1010 const gnash::MovieClip& si = m->getRootMovie();
1011 if (curframe + 1 >= si.get_frame_count()) {
1012 quit();
1016 if (_screenShotter.get() && _renderer.get()) {
1017 _screenShotter->screenShot(*_renderer, _advances, doDisplay ? 0 : &dis);
1020 // Only increment advances and check for exit condition when we've
1021 // really changed frame.
1022 if (advanced) {
1023 /// Quit if we've reached the frame advance limit.
1024 if (_maxAdvances && (_advances > _maxAdvances)) {
1025 quit();
1027 ++_advances;
1030 return advanced;
1033 void
1034 Gui::setScreenShotter(std::auto_ptr<ScreenShotter> ss)
1036 _screenShotter.reset(ss.release());
1039 void
1040 Gui::takeScreenShot()
1042 if (!_screenShotter.get()) {
1043 // If no ScreenShotter exists, none was requested at startup.
1044 // We use a default filename pattern.
1045 URL url(_runResources.streamProvider().baseURL());
1046 std::string::size_type p = url.path().rfind('/');
1047 const std::string& name = (p == std::string::npos) ? url.path() :
1048 url.path().substr(p + 1);
1049 const std::string& filename = "screenshot-" + name + "-%f";
1050 _screenShotter.reset(new ScreenShotter(filename, GNASH_FILETYPE_PNG));
1052 assert (_screenShotter.get());
1053 _screenShotter->now();
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<movie_root::InfoTree>
1096 Gui::getMovieInfo() const
1098 std::auto_ptr<movie_root::InfoTree> tr;
1100 if (!_stage) {
1101 return tr;
1104 tr.reset(new movie_root::InfoTree());
1106 // Top nodes for the tree:
1107 // 1. VM information
1108 // 2. "Stage" information
1109 // 3. ...
1111 movie_root::InfoTree::iterator topIter = tr->begin();
1112 movie_root::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, std::make_pair("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, std::make_pair("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, std::make_pair("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,
1151 std::make_pair("Active entity under mouse pointer", ss.str()));
1154 ch = stage.getEntityUnderPointer();
1155 if (ch) {
1156 std::stringstream ss;
1157 ss << ch->getTarget() << " (" + typeName(*ch)
1158 << " - depth:" << ch->get_depth()
1159 << ")";
1160 firstLevelIter = tr->append_child(topIter,
1161 std::make_pair("Topmost entity under mouse pointer", ss.str()));
1164 ch = stage.getDraggingCharacter();
1165 if (ch) {
1166 std::stringstream ss;
1167 ss << ch->getTarget() << " (" + typeName(*ch)
1168 << " - depth:" << ch->get_depth() << ")";
1169 firstLevelIter = tr->append_child(topIter,
1170 std::make_pair("Dragging character: ", ss.str()));
1174 /// GC row
1176 topIter = tr->insert(topIter, std::make_pair("GC Statistics", ""));
1177 GC::CollectablesCount cc;
1178 _stage->gc().countCollectables(cc);
1180 const std::string lbl = "GC managed ";
1181 for (GC::CollectablesCount::iterator i=cc.begin(), e=cc.end(); i!=e; ++i) {
1182 const std::string& typ = i->first;
1183 std::ostringstream ss;
1184 ss << i->second;
1185 firstLevelIter = tr->append_child(topIter,
1186 std::make_pair(lbl + typ, ss.str()));
1189 tr->sort(firstLevelIter.begin(), firstLevelIter.end());
1191 return tr;
1194 #endif
1196 #ifdef GNASH_FPS_DEBUG
1197 void
1198 Gui::fpsCounterTick()
1201 // increment this *before* the early return so that
1202 // frame count on exit is still valid
1203 ++fps_counter_total;
1205 if (! fps_timer_interval) {
1206 return;
1209 boost::uint64_t current_timer = clocktime::getTicks();
1211 // TODO: keep fps_timer_interval in milliseconds to avoid the multiplication
1212 // at each fpsCounterTick call...
1213 boost::uint64_t interval_ms = (boost::uint64_t)(fps_timer_interval * 1000.0);
1215 if (fps_counter_total==1) {
1216 fps_timer = current_timer;
1217 fps_start_timer = current_timer;
1220 ++fps_counter;
1222 if (current_timer - fps_timer >= interval_ms) {
1224 float secs = (current_timer - fps_timer) / 1000.0;
1225 float secs_total = (current_timer - fps_start_timer)/1000.0;
1227 float rate = fps_counter/secs;
1229 if (secs > 10000000) {
1230 // the timers are unsigned, so when the clock runs "backwards" it leads
1231 // to a very high difference value. In theory, this should never happen
1232 // with ticks, but it does on my machine (which may have a hw problem?).
1233 std::cerr << "Time glitch detected, need to restart FPS counters, sorry..." << std::endl;
1235 fps_timer = current_timer;
1236 fps_start_timer = current_timer;
1237 fps_counter_total = 0;
1238 fps_counter = 0;
1239 return;
1242 // first FPS message?
1243 if (fps_timer == fps_start_timer) { // they're ints, so we can compare
1244 fps_rate_min = rate;
1245 fps_rate_max = rate;
1246 } else {
1247 fps_rate_min = std::min<float>(fps_rate_min, rate);
1248 fps_rate_max = std::max<float>(fps_rate_max, rate);
1251 float avg = fps_counter_total / secs_total;
1253 //log_debug("Effective frame rate: %0.2f fps", (float)(fps_counter/secs));
1254 std::cerr << boost::format("Effective frame rate: %0.2f fps "
1255 "(min %0.2f, avg %0.2f, max %0.2f, "
1256 "%u frames in %0.1f secs total, "
1257 "dropped %u)") % rate %
1258 fps_rate_min % avg % fps_rate_max %
1259 fps_counter_total % secs_total %
1260 frames_dropped << std::endl;
1262 fps_counter = 0;
1263 fps_timer = current_timer;
1268 #endif
1270 void
1271 Gui::addFlashVars(Gui::VariableMap& from)
1273 for (VariableMap::iterator i=from.begin(), ie=from.end(); i!=ie; ++i) {
1274 _flashVars[i->first] = i->second;
1278 void
1279 Gui::setMovieDefinition(movie_definition* md)
1281 assert(!_movieDef);
1282 _movieDef = md;
1285 void
1286 Gui::setStage(movie_root* stage)
1288 assert(stage);
1289 assert(!_stage);
1290 _stage = stage;
1293 bool
1294 Gui::yesno(const std::string& question)
1296 log_error("This gui didn't override 'yesno', assuming 'yes' answer to "
1297 "question: %s", question);
1298 return true;
1301 void
1302 Gui::setQuality(Quality q)
1304 if (!_stage) {
1305 log_error("Gui::setQuality called before a movie_root was available");
1306 return;
1308 _stage->setQuality(q);
1311 Quality
1312 Gui::getQuality() const
1314 if (!_stage) {
1315 log_error("Gui::getQuality called before a movie_root was available");
1316 // just a guess..
1317 return QUALITY_HIGH;
1319 return _stage->getQuality();
1322 void
1323 Gui::setFDCallback(int fd, boost::function<void ()> callback)
1325 log_debug("Setting callback for fd #%d", fd);
1327 _fd_callbacks[fd] = callback;
1329 watchFD(fd);
1333 void
1334 Gui::callCallback(int fd)
1336 std::map<int, boost::function<void ()> >::iterator it = _fd_callbacks.find(fd);
1338 if (it == _fd_callbacks.end()) {
1339 log_error("Attempted to call a callback for an unregistered fd.");
1340 return;
1343 boost::function<void()>& f = it->second;
1345 f();
1347 // end of namespace
1350 // local Variables:
1351 // mode: C++
1352 // indent-tabs-mode: nil
1353 // End: