Fix test for bug #32625
[gnash.git] / libcore / Button.cpp
blobbfb35588a10e6932bc50873334cf4add27cb4991
1 // Button.cpp: Mouse-sensitive buttons, 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" // USE_SWF_TREE
23 #endif
25 #include "Button.h"
27 #include <boost/bind.hpp>
28 #include <utility>
30 #include "smart_ptr.h"
31 #include "DefineButtonTag.h"
32 #include "as_value.h"
33 #include "Button.h"
34 #include "ActionExec.h"
35 #include "MovieClip.h"
36 #include "movie_root.h"
37 #include "VM.h"
38 #include "NativeFunction.h"
39 #include "fn_call.h"
40 #include "ExecutableCode.h"
41 #include "namedStrings.h"
42 #include "StringPredicates.h"
43 #include "GnashKey.h"
44 #include "SoundInfoRecord.h"
45 #include "Global_as.h"
46 #include "RunResources.h"
47 #include "sound_definition.h"
48 #include "Transform.h"
50 /** \page buttons Buttons and mouse behaviour
52 Observations about button & mouse behavior
54 Entities that receive mouse events: only buttons and sprites, AFAIK
56 When the mouse button goes down, it becomes "captured" by whatever
57 element is topmost, directly below the mouse at that moment. While
58 the mouse is captured, no other entity receives mouse events,
59 regardless of how the mouse or other elements move.
61 The mouse remains captured until the mouse button goes up. The mouse
62 remains captured even if the element that captured it is removed from
63 the display list.
65 If the mouse isn't above a button or sprite when the mouse button goes
66 down, then the mouse is captured by the background (i.e. mouse events
67 just don't get sent, until the mouse button goes up again).
69 Mouse events:
71 +------------------+---------------+-------------------------------------+
72 | Event | Mouse Button | description |
73 =========================================================================
74 | onRollOver | up | sent to topmost entity when mouse |
75 | | | cursor initially goes over it |
76 +------------------+---------------+-------------------------------------+
77 | onRollOut | up | when mouse leaves entity, after |
78 | | | onRollOver |
79 +------------------+---------------+-------------------------------------+
80 | onPress | up -> down | sent to topmost entity when mouse |
81 | | | button goes down. onRollOver |
82 | | | always precedes onPress. Initiates |
83 | | | mouse capture. |
84 +------------------+---------------+-------------------------------------+
85 | onRelease | down -> up | sent to active entity if mouse goes |
86 | | | up while over the element |
87 +------------------+---------------+-------------------------------------+
88 | onDragOut | down | sent to active entity if mouse |
89 | | | is no longer over the entity |
90 +------------------+---------------+-------------------------------------+
91 | onReleaseOutside | down -> up | sent to active entity if mouse goes |
92 | | | up while not over the entity. |
93 | | | onDragOut always precedes |
94 | | | onReleaseOutside |
95 +------------------+---------------+-------------------------------------+
96 | onDragOver | down | sent to active entity if mouse is |
97 | | | dragged back over it after |
98 | | | onDragOut |
99 +------------------+---------------+-------------------------------------+
101 There is always one active entity at any given time (considering NULL to
102 be an active entity, representing the background, and other objects that
103 don't receive mouse events).
105 When the mouse button is up, the active entity is the topmost element
106 directly under the mouse pointer.
108 When the mouse button is down, the active entity remains whatever it
109 was when the button last went down.
111 The active entity is the only object that receives mouse events.
113 !!! The "trackAsMenu" property alters this behavior! If trackAsMenu
114 is set on the active entity, then onReleaseOutside is filtered out,
115 and onDragOver from another entity is allowed (from the background, or
116 another trackAsMenu entity). !!!
119 Pseudocode:
121 active_entity = NULL
122 mouse_button_state = UP
123 mouse_inside_entity_state = false
124 frame loop:
125 if mouse_button_state == DOWN
127 // Handle trackAsMenu
128 if (active_entity->trackAsMenu)
129 possible_entity = topmost entity below mouse
130 if (possible_entity != active_entity && possible_entity->trackAsMenu)
131 // Transfer to possible entity
132 active_entity = possible_entity
133 active_entity->onDragOver()
134 mouse_inside_entity_state = true;
136 // Handle onDragOut, onDragOver
137 if (mouse_inside_entity_state == false)
138 if (mouse is actually inside the active_entity)
139 // onDragOver
140 active_entity->onDragOver()
141 mouse_inside_entity_state = true;
143 else // mouse_inside_entity_state == true
144 if (mouse is actually outside the active_entity)
145 // onDragOut
146 active_entity->onDragOut()
147 mouse_inside_entity_state = false;
149 // Handle onRelease, onReleaseOutside
150 if (mouse button is up)
151 if (mouse_inside_entity_state)
152 // onRelease
153 active_entity->onRelease()
154 else
155 // onReleaseOutside
156 if (active_entity->trackAsMenu == false)
157 active_entity->onReleaseOutside()
158 mouse_button_state = UP
160 if mouse_button_state == UP
161 new_active_entity = topmost entity below the mouse
162 if (new_active_entity != active_entity)
163 // onRollOut, onRollOver
164 active_entity->onRollOut()
165 active_entity = new_active_entity
166 active_entity->onRollOver()
168 // Handle press
169 if (mouse button is down)
170 // onPress
171 active_entity->onPress()
172 mouse_inside_entity_state = true
173 mouse_button_state = DOWN
178 namespace gnash {
180 namespace {
181 as_value button_blendMode(const fn_call& fn);
182 as_value button_cacheAsBitmap(const fn_call& fn);
183 as_value button_filters(const fn_call& fn);
184 as_value button_scale9Grid(const fn_call& fn);
185 as_value button_setTabIndex(const fn_call& fn);
186 as_value button_getTabIndex(const fn_call& fn);
187 as_value button_getDepth(const fn_call& fn);
190 namespace {
192 class ButtonActionExecutor {
193 public:
194 ButtonActionExecutor(as_environment& env)
196 _env(env)
199 void operator() (const action_buffer& ab)
201 ActionExec exec(ab, _env);
202 exec();
204 private:
205 as_environment& _env;
208 class ButtonActionPusher {
209 public:
210 ButtonActionPusher(movie_root& mr, DisplayObject* this_ptr)
212 _mr(mr),
213 _tp(this_ptr)
216 void operator()(const action_buffer& ab)
218 _mr.pushAction(ab, _tp);
221 private:
222 movie_root& _mr;
223 DisplayObject* _tp;
228 namespace {
229 void addInstanceProperty(Button& b, DisplayObject* d) {
230 if (!d) return;
231 const ObjectURI& name = d->get_name();
232 if (name.empty()) return;
233 getObject(&b)->init_member(name, getObject(d), 0);
236 void removeInstanceProperty(Button& b, DisplayObject* d) {
237 if (!d) return;
238 const ObjectURI& name = d->get_name();
239 if (name.empty()) return;
240 getObject(&b)->delProperty(name);
244 /// Predicates for standard algorithms.
246 /// Depth comparator for DisplayObjects.
247 static bool charDepthLessThen(const DisplayObject* ch1, const DisplayObject* ch2)
249 return ch1->get_depth() < ch2->get_depth();
252 /// Predicate for finding active DisplayObjects.
254 /// Returns true if the DisplayObject should be skipped:
255 /// 1) if it is NULL, or
256 /// 2) if we don't want unloaded DisplayObjects and the DisplayObject is unloaded.
257 static bool isCharacterNull(DisplayObject* ch, bool includeUnloaded)
259 return (!ch || (!includeUnloaded && ch->unloaded()));
262 static void
263 attachButtonInterface(as_object& o)
266 const int unprotected = 0;
267 o.init_member(NSV::PROP_ENABLED, true, unprotected);
268 o.init_member("useHandCursor", true, unprotected);
270 const int swf8Flags = PropFlags::onlySWF8Up;
271 VM& vm = getVM(o);
273 o.init_property("tabIndex", *vm.getNative(105, 1), *vm.getNative(105, 2),
274 swf8Flags);
276 o.init_member("getDepth", vm.getNative(105, 3), unprotected);
278 NativeFunction* gs;
279 gs = vm.getNative(105, 4);
280 o.init_property("scale9Grid", *gs, *gs, swf8Flags);
281 gs = vm.getNative(105, 5);
282 o.init_property("filters", *gs, *gs, swf8Flags);
283 gs = vm.getNative(105, 6);
284 o.init_property("cacheAsBitmap", *gs, *gs, swf8Flags);
285 gs = vm.getNative(105, 7);
286 o.init_property("blendMode", *gs, *gs, swf8Flags);
290 Button::Button(as_object* object, const SWF::DefineButtonTag* def,
291 DisplayObject* parent)
293 InteractiveObject(object, parent),
294 _mouseState(MOUSESTATE_UP),
295 _def(def)
297 assert(object);
299 // check up presence Key events
300 if (_def->hasKeyPressHandler()) {
301 stage().add_key_listener(this);
306 Button::~Button()
308 stage().remove_key_listener(this);
311 bool
312 Button::trackAsMenu()
314 // TODO: check whether the AS or the tag value takes precedence.
315 as_object* obj = getObject(this);
316 assert(obj);
318 VM& vm = getVM(*obj);
320 as_value track;
321 // TODO: use NSV
322 const ObjectURI& propTrackAsMenu = getURI(vm, "trackAsMenu");
323 if (obj->get_member(propTrackAsMenu, &track)) {
324 return toBool(track, vm);
326 if (_def) return _def->trackAsMenu();
327 return false;
330 bool
331 Button::isEnabled()
333 as_object* obj = getObject(this);
334 assert(obj);
336 as_value enabled;
337 if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) return false;
339 return toBool(enabled, getVM(*obj));
343 void
344 Button::notifyEvent(const event_id& id)
346 if (unloaded()) {
347 // We dont' respond to events while unloaded
348 // See bug #22982
349 return;
352 // We only respond keypress events
353 if ( id.id() != event_id::KEY_PRESS ) return;
355 // We only respond to valid key code (should we assert here?)
356 if ( id.keyCode() == key::INVALID ) return;
358 ButtonActionPusher xec(stage(), this);
359 _def->forEachTrigger(id, xec);
362 bool
363 Button::handleFocus() {
364 /// Nothing to do, but can receive focus.
365 return false;
369 void
370 Button::display(Renderer& renderer, const Transform& base)
372 const DisplayObject::MaskRenderer mr(renderer, *this);
374 const Transform xform = base * transform();
376 DisplayObjects actChars;
377 getActiveCharacters(actChars);
379 // TODO: by keeping chars sorted by depth we'd avoid the sort on display
380 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
382 for (DisplayObjects::iterator it = actChars.begin(), e = actChars.end();
383 it != e; ++it) {
384 (*it)->display(renderer, xform);
387 clear_invalidated();
391 // Return the topmost entity that the given point covers. NULL if none.
392 // I.e. check against ourself.
393 InteractiveObject*
394 Button::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
396 if (!visible() || !isEnabled())
398 return 0;
401 //-------------------------------------------------
402 // Check our active and visible children first
403 //-------------------------------------------------
405 DisplayObjects actChars;
406 getActiveCharacters(actChars);
408 if ( ! actChars.empty() )
410 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
412 SWFMatrix m = getMatrix(*this);
413 point p(x, y);
414 m.invert().transform(p);
416 for (DisplayObjects::reverse_iterator it = actChars.rbegin(),
417 itE=actChars.rend(); it!=itE; ++it)
419 DisplayObject* ch = *it;
420 if ( ! ch->visible() ) continue;
421 InteractiveObject *hit = ch->topmostMouseEntity(p.x, p.y);
422 if ( hit ) return hit;
426 //-------------------------------------------------
427 // If that failed, check our hit area
428 //-------------------------------------------------
430 // Find hit DisplayObjects
431 if ( _hitCharacters.empty() ) return 0;
433 // point is in p's space,
434 // we need to convert it in world space
435 point wp(x,y);
436 DisplayObject* p = parent();
437 if (p) {
438 getWorldMatrix(*p).transform(wp);
441 for (DisplayObjects::const_iterator i = _hitCharacters.begin(),
442 e = _hitCharacters.end(); i !=e; ++i)
444 if ((*i)->pointInVisibleShape(wp.x, wp.y))
446 // The mouse is inside the shape.
447 return this;
451 return NULL;
455 void
456 Button::mouseEvent(const event_id& event)
458 if ( unloaded() )
460 // We don't respond to events while unloaded. See bug #22982.
461 log_debug("Button %s received %s button event while unloaded: ignored",
462 getTarget(), event);
463 return;
466 MouseState new_state = _mouseState;
468 // Set our mouse state (so we know how to render).
469 switch (event.id())
471 case event_id::ROLL_OUT:
472 case event_id::RELEASE_OUTSIDE:
473 new_state = MOUSESTATE_UP;
474 break;
476 case event_id::RELEASE:
477 case event_id::ROLL_OVER:
478 case event_id::DRAG_OUT:
479 case event_id::MOUSE_UP:
480 new_state = MOUSESTATE_OVER;
481 break;
483 case event_id::PRESS:
484 case event_id::DRAG_OVER:
485 case event_id::MOUSE_DOWN:
486 new_state = MOUSESTATE_DOWN;
487 break;
489 default:
490 //abort(); // missed a case?
491 log_error(_("Unhandled button event %s"), event);
492 break;
495 set_current_state(new_state);
497 // Button transition sounds.
498 do {
500 if (!_def->hasSound()) break;
502 // Check if there is a sound handler
503 sound::sound_handler* s = getRunResources(*getObject(this)).soundHandler();
504 if (!s) break;
506 int bi; // button sound array index [0..3]
508 switch (event.id())
510 case event_id::ROLL_OUT:
511 bi = 0;
512 break;
513 case event_id::ROLL_OVER:
514 bi = 1;
515 break;
516 case event_id::PRESS:
517 bi = 2;
518 break;
519 case event_id::RELEASE:
520 bi = 3;
521 break;
522 default:
523 bi = -1;
524 break;
527 // no sound for this transition
528 if (bi < 0) break;
530 const SWF::DefineButtonSoundTag::ButtonSound& bs =
531 _def->buttonSound(bi);
533 // character zero is considered as null character
534 if (!bs.soundID) break;
536 // No actual sound ?
537 if (!bs.sample) break;
539 if (bs.soundInfo.stopPlayback) {
540 s->stop_sound(bs.sample->m_sound_handler_id);
542 else {
543 const SWF::SoundInfoRecord& sinfo = bs.soundInfo;
545 const sound::SoundEnvelopes* env =
546 sinfo.envelopes.empty() ? 0 : &sinfo.envelopes;
548 s->startSound(bs.sample->m_sound_handler_id,
549 bs.soundInfo.loopCount,
550 env, // envelopes
551 !sinfo.noMultiple, // allow multiple instances ?
552 sinfo.inPoint,
553 sinfo.outPoint
557 } while(0);
559 // From: "ActionScript - The Definitive Guide" by Colin Moock
560 // (chapter 10: Events and Event Handlers)
562 // "Event-based code [..] is said to be executed asynchronously
563 // because the triggering of events can occur at arbitrary times."
565 // We'll push to the global list. The movie_root will process
566 // the action queue on mouse event.
569 movie_root& mr = stage();
571 ButtonActionPusher xec(mr, this);
572 _def->forEachTrigger(event, xec);
574 // check for built-in event handler.
575 std::auto_ptr<ExecutableCode> code (get_event_handler(event));
576 if (code.get()) {
577 mr.pushAction(code, movie_root::PRIORITY_DOACTION);
580 sendEvent(*getObject(this), get_environment(), event.functionURI());
584 void
585 Button::getActiveCharacters(ConstDisplayObjects& list) const
587 list.clear();
589 // Copy all the DisplayObjects to the new list, skipping NULL and unloaded
590 // DisplayObjects.
591 std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
592 std::back_inserter(list),
593 boost::bind(&isCharacterNull, _1, false));
598 void
599 Button::getActiveCharacters(DisplayObjects& list, bool includeUnloaded)
601 list.clear();
603 // Copy all the DisplayObjects to the new list, skipping NULL
604 // DisplayObjects, optionally including unloaded DisplayObjects.
605 std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
606 std::back_inserter(list),
607 boost::bind(&isCharacterNull, _1, includeUnloaded));
611 void
612 Button::get_active_records(ActiveRecords& list, MouseState state)
614 list.clear();
616 using namespace SWF;
617 const DefineButtonTag::ButtonRecords& br = _def->buttonRecords();
618 size_t index = 0;
620 for (DefineButtonTag::ButtonRecords::const_iterator i = br.begin(),
621 e = br.end(); i != e; ++i, ++index)
623 const ButtonRecord& rec =*i;
624 if (rec.hasState(state)) list.insert(index);
628 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
629 static void dump(Button::DisplayObjects& chars, std::stringstream& ss)
631 for (size_t i=0, e=chars.size(); i<e; ++i)
633 ss << "Record" << i << ": ";
634 DisplayObject* ch = chars[i];
635 if ( ! ch ) ss << "NULL.";
636 else
638 ss << ch->getTarget() << " (depth:" <<
639 ch->get_depth()-DisplayObject::staticDepthOffset-1
640 << " unloaded:" << ch->unloaded() <<
641 " destroyed:" << ch->isDestroyed() << ")";
643 ss << std::endl;
646 #endif
648 void
649 Button::set_current_state(MouseState new_state)
651 if (new_state == _mouseState)
652 return;
654 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
655 std::stringstream ss;
656 ss << "at set_current_state enter: " << std::endl;
657 dump(_stateCharacters, ss);
658 log_debug("%s", ss.str());
659 #endif
661 // Get new state records
662 ActiveRecords newChars;
663 get_active_records(newChars, new_state);
665 // For each possible record, check if it should still be there
666 for (size_t i=0, e=_stateCharacters.size(); i<e; ++i)
668 DisplayObject* oldch = _stateCharacters[i];
669 bool shouldBeThere = ( newChars.find(i) != newChars.end() );
671 if ( ! shouldBeThere )
674 // is there, but is unloaded: destroy, clear slot and go on
675 if ( oldch && oldch->unloaded() ) {
676 removeInstanceProperty(*this, oldch);
677 if ( ! oldch->isDestroyed() ) oldch->destroy();
678 _stateCharacters[i] = NULL;
679 oldch = NULL;
682 if ( oldch ) // the one we have should not be there... unload!
684 set_invalidated();
686 if ( ! oldch->unload() )
688 // No onUnload handler: destroy and clear slot
689 removeInstanceProperty(*this, oldch);
690 if (!oldch->isDestroyed()) oldch->destroy();
691 _stateCharacters[i] = NULL;
693 else
695 // onUnload handler: shift depth and keep slot
696 int oldDepth = oldch->get_depth();
697 int newDepth = DisplayObject::removedDepthOffset - oldDepth;
698 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
699 log_debug("Removed button record shifted from depth %d to depth %d", oldDepth, newDepth);
700 #endif
701 oldch->set_depth(newDepth);
705 else // should be there
707 // Is there already, but is unloaded: destroy and consider as gone
708 if ( oldch && oldch->unloaded() )
710 removeInstanceProperty(*this, oldch);
711 if ( ! oldch->isDestroyed() ) oldch->destroy();
712 _stateCharacters[i] = NULL;
713 oldch = NULL;
716 if (!oldch) {
717 // Not there, instantiate
718 const SWF::ButtonRecord& rec = _def->buttonRecords()[i];
719 DisplayObject* ch = rec.instantiate(this);
721 set_invalidated();
722 _stateCharacters[i] = ch;
723 addInstanceProperty(*this, ch);
724 ch->construct();
729 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
730 ss.str("");
731 ss << "at set_current_state end: " << std::endl;
732 dump(_stateCharacters, ss);
733 log_debug("%s", ss.str());
734 #endif
736 // Remember current state
737 _mouseState=new_state;
741 void
742 Button::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
745 // Not visible anyway
746 if (!visible()) return;
748 ranges.add(m_old_invalidated_ranges);
750 DisplayObjects actChars;
751 getActiveCharacters(actChars);
752 std::for_each(actChars.begin(), actChars.end(),
753 boost::bind(&DisplayObject::add_invalidated_bounds, _1,
754 boost::ref(ranges), force || invalidated())
758 SWFRect
759 Button::getBounds() const
761 SWFRect allBounds;
763 typedef std::vector<const DisplayObject*> Chars;
764 Chars actChars;
765 getActiveCharacters(actChars);
766 for(Chars::const_iterator i=actChars.begin(),e=actChars.end(); i!=e; ++i)
768 const DisplayObject* ch = *i;
769 // Child bounds need be transformed in our coordinate space
770 SWFRect lclBounds = ch->getBounds();
771 SWFMatrix m = getMatrix(*ch);
772 allBounds.expand_to_transformed_rect(m, lclBounds);
775 return allBounds;
778 bool
779 Button::pointInShape(boost::int32_t x, boost::int32_t y) const
781 typedef std::vector<const DisplayObject*> Chars;
782 Chars actChars;
783 getActiveCharacters(actChars);
784 for(Chars::const_iterator i=actChars.begin(),e=actChars.end(); i!=e; ++i)
786 const DisplayObject* ch = *i;
787 if (ch->pointInShape(x,y)) return true;
789 return false;
792 void
793 Button::construct(as_object* initObj)
795 // This can happen if attachMovie is called with an exported Button and
796 // an init object. The attachment happens, but the init object is not used
797 // (see misc-ming.all/attachMovieTest.swf).
798 if (initObj) {
799 IF_VERBOSE_ASCODING_ERRORS(
800 log_aserror("Button placed with an init object. This will "
801 "be ignored.");
805 saveOriginalTarget(); // for soft refs
807 // Don't register this button instance as a live DisplayObject.
809 // Instantiate the hit DisplayObjects
810 ActiveRecords hitChars;
811 get_active_records(hitChars, MOUSESTATE_HIT);
812 for (ActiveRecords::iterator i=hitChars.begin(),e=hitChars.end(); i!=e; ++i)
814 const SWF::ButtonRecord& rec = _def->buttonRecords()[*i];
816 // These should not be named!
817 DisplayObject* ch = rec.instantiate(this, false);
818 _hitCharacters.push_back(ch);
821 // Setup the state DisplayObjects container
822 // It will have a slot for each DisplayObject record.
823 // Some slots will probably be never used (consider HIT-only records)
824 // but for now this direct corrispondence between record number
825 // and active DisplayObject will be handy.
826 _stateCharacters.resize(_def->buttonRecords().size());
828 // Instantiate the default state DisplayObjects
829 ActiveRecords upChars;
830 get_active_records(upChars, MOUSESTATE_UP);
832 for (ActiveRecords::iterator i = upChars.begin(), e=upChars.end();
833 i != e; ++i)
835 int rno = *i;
836 const SWF::ButtonRecord& rec = _def->buttonRecords()[rno];
838 DisplayObject* ch = rec.instantiate(this);
840 _stateCharacters[rno] = ch;
841 addInstanceProperty(*this, ch);
842 ch->construct();
845 // There is no INITIALIZE/CONSTRUCT/LOAD/ENTERFRAME/UNLOAD event
846 // for Buttons
849 void
850 Button::markOwnResources() const
853 // Mark state DisplayObjects as reachable
854 for (DisplayObjects::const_iterator i = _stateCharacters.begin(),
855 e = _stateCharacters.end(); i != e; ++i)
857 DisplayObject* ch = *i;
858 if (ch) ch->setReachable();
861 // Mark hit DisplayObjects as reachable
862 std::for_each(_hitCharacters.begin(), _hitCharacters.end(),
863 std::mem_fun(&DisplayObject::setReachable));
867 bool
868 Button::unloadChildren()
871 bool childsHaveUnload = false;
873 // We need to unload all children, or the global instance list
874 // will keep growing forever !
875 for (DisplayObjects::iterator i = _stateCharacters.begin(),
876 e = _stateCharacters.end(); i != e; ++i)
878 DisplayObject* ch = *i;
879 if (!ch || ch->unloaded()) continue;
880 if (ch->unload()) childsHaveUnload = true;
883 // NOTE: we don't need to ::unload or ::destroy here
884 // as the _hitCharacters are never placed on stage.
885 // As an optimization we might not even instantiate
886 // them, and only use the definition and the
887 // associated transform SWFMatrix... (would take
888 // hit instance off the GC).
889 _hitCharacters.clear();
891 return childsHaveUnload;
894 void
895 Button::destroy()
898 for (DisplayObjects::iterator i = _stateCharacters.begin(),
899 e=_stateCharacters.end(); i != e; ++i) {
900 DisplayObject* ch = *i;
901 if (!ch || ch->isDestroyed()) continue;
902 ch->destroy();
905 // NOTE: we don't need to ::unload or ::destroy here
906 // as the _hitCharacters are never placed on stage.
907 // As an optimization we might not even instantiate
908 // them, and only use the definition and the
909 // associated transform SWFMatrix... (would take
910 // hit instance off the GC).
911 _hitCharacters.clear();
913 DisplayObject::destroy();
917 Button::getDefinitionVersion() const
919 return _def->getSWFVersion();
922 static as_value
923 button_ctor(const fn_call& /*fn*/)
925 return as_value();
928 void
929 button_class_init(as_object& global, const ObjectURI& uri)
931 // This is going to be the global Button "class"/"function"
932 Global_as& gl = getGlobal(global);
933 as_object* proto = createObject(gl);
934 as_object* cl = gl.createClass(&button_ctor, proto);
935 attachButtonInterface(*proto);
937 // Register _global.MovieClip
938 global.init_member(uri, cl, as_object::DefaultFlags);
941 void
942 registerButtonNative(as_object& global)
944 VM& vm = getVM(global);
945 vm.registerNative(button_setTabIndex, 105, 1);
946 vm.registerNative(button_getTabIndex, 105, 2);
947 vm.registerNative(button_getDepth, 105, 3);
948 vm.registerNative(button_scale9Grid, 105, 4);
949 vm.registerNative(button_filters, 105, 5);
950 vm.registerNative(button_cacheAsBitmap, 105, 6);
951 vm.registerNative(button_blendMode, 105, 7);
954 #ifdef USE_SWFTREE
955 DisplayObject::InfoTree::iterator
956 Button::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
958 InfoTree::iterator selfIt = DisplayObject::getMovieInfo(tr, it);
959 std::ostringstream os;
961 DisplayObjects actChars;
962 getActiveCharacters(actChars, true);
963 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
965 os << actChars.size() << " active DisplayObjects for state " <<
966 mouseStateName(_mouseState);
967 InfoTree::iterator localIter = tr.append_child(selfIt,
968 std::make_pair(_("Button state"), os.str()));
970 os.str("");
971 os << std::boolalpha << isEnabled();
972 localIter = tr.append_child(selfIt, std::make_pair(_("Enabled"), os.str()));
974 std::for_each(actChars.begin(), actChars.end(),
975 boost::bind(&DisplayObject::getMovieInfo, _1, tr, localIter));
977 return selfIt;
980 #endif
982 const char*
983 Button::mouseStateName(MouseState s)
985 switch (s)
987 case MOUSESTATE_UP: return "UP";
988 case MOUSESTATE_DOWN: return "DOWN";
989 case MOUSESTATE_OVER: return "OVER";
990 case MOUSESTATE_HIT: return "HIT";
991 default: std::abort();
995 namespace {
997 as_value
998 button_blendMode(const fn_call& fn)
1000 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1001 UNUSED(obj);
1002 return as_value();
1005 as_value
1006 button_cacheAsBitmap(const fn_call& fn)
1008 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1009 UNUSED(obj);
1010 return as_value();
1013 as_value
1014 button_filters(const fn_call& fn)
1016 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1017 UNUSED(obj);
1018 return as_value();
1021 as_value
1022 button_scale9Grid(const fn_call& fn)
1024 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1025 UNUSED(obj);
1026 return as_value();
1029 as_value
1030 button_getTabIndex(const fn_call& fn)
1032 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1033 UNUSED(obj);
1034 return as_value();
1037 as_value
1038 button_setTabIndex(const fn_call& fn)
1040 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1041 UNUSED(obj);
1042 return as_value();
1045 as_value
1046 button_getDepth(const fn_call& fn)
1048 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1049 UNUSED(obj);
1050 return as_value();
1053 } // anonymous namespace
1054 } // end of namespace gnash
1057 // Local Variables:
1058 // mode: C++
1059 // c-basic-offset: 8
1060 // tab-width: 8
1061 // indent-tabs-mode: t
1062 // End: