update copyright date
[gnash.git] / libcore / Button.cpp
bloba70ee5cbc73312fa941b5a3e0cdce763fcca9e52
1 // Button.cpp: Mouse-sensitive buttons, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
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>
29 #include <functional>
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
210 public:
211 ButtonActionPusher(movie_root& mr, DisplayObject* this_ptr)
213 _mr(mr),
214 _tp(this_ptr)
217 void operator()(const action_buffer& ab)
219 _mr.pushAction(ab, _tp);
222 private:
223 movie_root& _mr;
224 DisplayObject* _tp;
229 namespace {
230 void addInstanceProperty(Button& b, DisplayObject* d) {
231 if (!d) return;
232 const ObjectURI& name = d->get_name();
233 if (name.empty()) return;
234 getObject(&b)->init_member(name, getObject(d), 0);
237 void removeInstanceProperty(Button& b, DisplayObject* d) {
238 if (!d) return;
239 const ObjectURI& name = d->get_name();
240 if (name.empty()) return;
241 getObject(&b)->delProperty(name);
245 /// Predicates for standard algorithms.
247 /// Depth comparator for DisplayObjects.
248 static bool charDepthLessThen(const DisplayObject* ch1, const DisplayObject* ch2)
250 return ch1->get_depth() < ch2->get_depth();
253 /// Predicate for finding active DisplayObjects.
255 /// Returns true if the DisplayObject should be skipped:
256 /// 1) if it is NULL, or
257 /// 2) if we don't want unloaded DisplayObjects and the DisplayObject is unloaded.
258 static bool isCharacterNull(DisplayObject* ch, bool includeUnloaded)
260 return (!ch || (!includeUnloaded && ch->unloaded()));
263 static void
264 attachButtonInterface(as_object& o)
267 const int unprotected = 0;
268 o.init_member(NSV::PROP_ENABLED, true, unprotected);
269 o.init_member("useHandCursor", true, unprotected);
271 const int swf8Flags = PropFlags::onlySWF8Up;
272 VM& vm = getVM(o);
274 o.init_property("tabIndex", *vm.getNative(105, 1), *vm.getNative(105, 2),
275 swf8Flags);
277 o.init_member("getDepth", vm.getNative(105, 3), unprotected);
279 NativeFunction* gs;
280 gs = vm.getNative(105, 4);
281 o.init_property("scale9Grid", *gs, *gs, swf8Flags);
282 gs = vm.getNative(105, 5);
283 o.init_property("filters", *gs, *gs, swf8Flags);
284 gs = vm.getNative(105, 6);
285 o.init_property("cacheAsBitmap", *gs, *gs, swf8Flags);
286 gs = vm.getNative(105, 7);
287 o.init_property("blendMode", *gs, *gs, swf8Flags);
291 Button::Button(as_object* object, const SWF::DefineButtonTag* def,
292 DisplayObject* parent)
294 InteractiveObject(object, parent),
295 _mouseState(MOUSESTATE_UP),
296 _def(def)
298 assert(object);
301 Button::~Button()
305 bool
306 Button::trackAsMenu()
308 // TODO: check whether the AS or the tag value takes precedence.
309 as_object* obj = getObject(this);
310 assert(obj);
312 VM& vm = getVM(*obj);
314 as_value track;
315 // TODO: use NSV
316 const ObjectURI& propTrackAsMenu = getURI(vm, "trackAsMenu");
317 if (obj->get_member(propTrackAsMenu, &track)) {
318 return toBool(track, vm);
320 if (_def) return _def->trackAsMenu();
321 return false;
324 bool
325 Button::isEnabled()
327 as_object* obj = getObject(this);
328 assert(obj);
330 as_value enabled;
331 if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) return false;
333 return toBool(enabled, getVM(*obj));
337 void
338 Button::keyPress(key::code c)
340 if (unloaded()) {
341 // We don't respond to events while unloaded
342 // See bug #22982
343 return;
346 ButtonActionPusher xec(stage(), this);
347 _def->forEachTrigger(event_id(event_id::KEY_PRESS, c), xec);
350 bool
351 Button::handleFocus()
353 /// Nothing to do, but can receive focus.
354 return false;
358 void
359 Button::display(Renderer& renderer, const Transform& base)
361 const DisplayObject::MaskRenderer mr(renderer, *this);
363 const Transform xform = base * transform();
365 DisplayObjects actChars;
366 getActiveCharacters(actChars);
368 // TODO: by keeping chars sorted by depth we'd avoid the sort on display
369 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
371 for (DisplayObjects::iterator it = actChars.begin(), e = actChars.end();
372 it != e; ++it) {
373 (*it)->display(renderer, xform);
376 clear_invalidated();
380 // Return the topmost entity that the given point covers. NULL if none.
381 // I.e. check against ourself.
382 InteractiveObject*
383 Button::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
385 if (!visible() || !isEnabled())
387 return 0;
390 //-------------------------------------------------
391 // Check our active and visible children first
392 //-------------------------------------------------
394 DisplayObjects actChars;
395 getActiveCharacters(actChars);
397 if ( ! actChars.empty() )
399 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
401 SWFMatrix m = getMatrix(*this);
402 point p(x, y);
403 m.invert().transform(p);
405 for (DisplayObjects::reverse_iterator it = actChars.rbegin(),
406 itE=actChars.rend(); it!=itE; ++it)
408 DisplayObject* ch = *it;
409 if ( ! ch->visible() ) continue;
410 InteractiveObject *hit = ch->topmostMouseEntity(p.x, p.y);
411 if ( hit ) return hit;
415 //-------------------------------------------------
416 // If that failed, check our hit area
417 //-------------------------------------------------
419 // Find hit DisplayObjects
420 if ( _hitCharacters.empty() ) return 0;
422 // point is in p's space,
423 // we need to convert it in world space
424 point wp(x,y);
425 DisplayObject* p = parent();
426 if (p) {
427 getWorldMatrix(*p).transform(wp);
430 for (DisplayObjects::const_iterator i = _hitCharacters.begin(),
431 e = _hitCharacters.end(); i !=e; ++i)
433 if ((*i)->pointInVisibleShape(wp.x, wp.y))
435 // The mouse is inside the shape.
436 return this;
440 return NULL;
444 void
445 Button::mouseEvent(const event_id& event)
447 if (unloaded()) {
448 // We don't respond to events while unloaded. See bug #22982.
449 return;
452 MouseState new_state = _mouseState;
454 // Set our mouse state (so we know how to render).
455 switch (event.id())
457 case event_id::ROLL_OUT:
458 case event_id::RELEASE_OUTSIDE:
459 new_state = MOUSESTATE_UP;
460 break;
462 case event_id::RELEASE:
463 case event_id::ROLL_OVER:
464 case event_id::DRAG_OUT:
465 case event_id::MOUSE_UP:
466 new_state = MOUSESTATE_OVER;
467 break;
469 case event_id::PRESS:
470 case event_id::DRAG_OVER:
471 case event_id::MOUSE_DOWN:
472 new_state = MOUSESTATE_DOWN;
473 break;
475 default:
476 //abort(); // missed a case?
477 log_error(_("Unhandled button event %s"), event);
478 break;
481 set_current_state(new_state);
483 // Button transition sounds.
484 do {
486 if (!_def->hasSound()) break;
488 // Check if there is a sound handler
489 sound::sound_handler* s = getRunResources(*getObject(this)).soundHandler();
490 if (!s) break;
492 int bi; // button sound array index [0..3]
494 switch (event.id())
496 case event_id::ROLL_OUT:
497 bi = 0;
498 break;
499 case event_id::ROLL_OVER:
500 bi = 1;
501 break;
502 case event_id::PRESS:
503 bi = 2;
504 break;
505 case event_id::RELEASE:
506 bi = 3;
507 break;
508 default:
509 bi = -1;
510 break;
513 // no sound for this transition
514 if (bi < 0) break;
516 const SWF::DefineButtonSoundTag::ButtonSound& bs =
517 _def->buttonSound(bi);
519 // character zero is considered as null character
520 if (!bs.soundID) break;
522 // No actual sound ?
523 if (!bs.sample) break;
525 if (bs.soundInfo.stopPlayback) {
526 s->stopEventSound(bs.sample->m_sound_handler_id);
528 else {
529 const SWF::SoundInfoRecord& sinfo = bs.soundInfo;
531 const sound::SoundEnvelopes* env =
532 sinfo.envelopes.empty() ? 0 : &sinfo.envelopes;
534 s->startSound(bs.sample->m_sound_handler_id,
535 bs.soundInfo.loopCount,
536 env, // envelopes
537 !sinfo.noMultiple, // allow multiple instances ?
538 sinfo.inPoint,
539 sinfo.outPoint
543 } while(0);
545 // From: "ActionScript - The Definitive Guide" by Colin Moock
546 // (chapter 10: Events and Event Handlers)
548 // "Event-based code [..] is said to be executed asynchronously
549 // because the triggering of events can occur at arbitrary times."
551 // We'll push to the global list. The movie_root will process
552 // the action queue on mouse event.
555 movie_root& mr = stage();
557 ButtonActionPusher xec(mr, this);
558 _def->forEachTrigger(event, xec);
560 // check for built-in event handler.
561 std::auto_ptr<ExecutableCode> code (get_event_handler(event));
562 if (code.get()) {
563 mr.pushAction(code, movie_root::PRIORITY_DOACTION);
566 sendEvent(*getObject(this), get_environment(), event.functionURI());
570 void
571 Button::getActiveCharacters(ConstDisplayObjects& list) const
573 list.clear();
575 // Copy all the DisplayObjects to the new list, skipping NULL and unloaded
576 // DisplayObjects.
577 std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
578 std::back_inserter(list),
579 boost::bind(&isCharacterNull, _1, false));
584 void
585 Button::getActiveCharacters(DisplayObjects& list, bool includeUnloaded)
587 list.clear();
589 // Copy all the DisplayObjects to the new list, skipping NULL
590 // DisplayObjects, optionally including unloaded DisplayObjects.
591 std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
592 std::back_inserter(list),
593 boost::bind(&isCharacterNull, _1, includeUnloaded));
597 void
598 Button::get_active_records(ActiveRecords& list, MouseState state)
600 list.clear();
602 using namespace SWF;
603 const DefineButtonTag::ButtonRecords& br = _def->buttonRecords();
604 size_t index = 0;
606 for (DefineButtonTag::ButtonRecords::const_iterator i = br.begin(),
607 e = br.end(); i != e; ++i, ++index)
609 const ButtonRecord& rec =*i;
610 if (rec.hasState(state)) list.insert(index);
614 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
615 static void dump(Button::DisplayObjects& chars, std::stringstream& ss)
617 for (size_t i=0, e=chars.size(); i<e; ++i)
619 ss << "Record" << i << ": ";
620 DisplayObject* ch = chars[i];
621 if ( ! ch ) ss << "NULL.";
622 else
624 ss << ch->getTarget() << " (depth:" <<
625 ch->get_depth()-DisplayObject::staticDepthOffset-1
626 << " unloaded:" << ch->unloaded() <<
627 " destroyed:" << ch->isDestroyed() << ")";
629 ss << std::endl;
632 #endif
634 void
635 Button::set_current_state(MouseState new_state)
637 if (new_state == _mouseState)
638 return;
640 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
641 std::stringstream ss;
642 ss << "at set_current_state enter: " << std::endl;
643 dump(_stateCharacters, ss);
644 log_debug("%s", ss.str());
645 #endif
647 // Get new state records
648 ActiveRecords newChars;
649 get_active_records(newChars, new_state);
651 // For each possible record, check if it should still be there
652 for (size_t i=0, e=_stateCharacters.size(); i<e; ++i)
654 DisplayObject* oldch = _stateCharacters[i];
655 bool shouldBeThere = ( newChars.find(i) != newChars.end() );
657 if ( ! shouldBeThere )
660 // is there, but is unloaded: destroy, clear slot and go on
661 if ( oldch && oldch->unloaded() ) {
662 removeInstanceProperty(*this, oldch);
663 if ( ! oldch->isDestroyed() ) oldch->destroy();
664 _stateCharacters[i] = NULL;
665 oldch = NULL;
668 if ( oldch ) // the one we have should not be there... unload!
670 set_invalidated();
672 if ( ! oldch->unload() )
674 // No onUnload handler: destroy and clear slot
675 removeInstanceProperty(*this, oldch);
676 if (!oldch->isDestroyed()) oldch->destroy();
677 _stateCharacters[i] = NULL;
679 else
681 // onUnload handler: shift depth and keep slot
682 int oldDepth = oldch->get_depth();
683 int newDepth = DisplayObject::removedDepthOffset - oldDepth;
684 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
685 log_debug("Removed button record shifted from depth %d to depth %d",
686 oldDepth, newDepth);
687 #endif
688 oldch->set_depth(newDepth);
692 else // should be there
694 // Is there already, but is unloaded: destroy and consider as gone
695 if ( oldch && oldch->unloaded() )
697 removeInstanceProperty(*this, oldch);
698 if ( ! oldch->isDestroyed() ) oldch->destroy();
699 _stateCharacters[i] = NULL;
700 oldch = NULL;
703 if (!oldch) {
704 // Not there, instantiate
705 const SWF::ButtonRecord& rec = _def->buttonRecords()[i];
706 DisplayObject* ch = rec.instantiate(this);
708 set_invalidated();
709 _stateCharacters[i] = ch;
710 addInstanceProperty(*this, ch);
711 ch->construct();
716 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
717 ss.str("");
718 ss << "at set_current_state end: " << std::endl;
719 dump(_stateCharacters, ss);
720 log_debug("%s", ss.str());
721 #endif
723 // Remember current state
724 _mouseState = new_state;
728 void
729 Button::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
732 // Not visible anyway
733 if (!visible()) return;
735 ranges.add(m_old_invalidated_ranges);
737 DisplayObjects actChars;
738 getActiveCharacters(actChars);
739 std::for_each(actChars.begin(), actChars.end(),
740 boost::bind(&DisplayObject::add_invalidated_bounds, _1,
741 boost::ref(ranges), force || invalidated())
745 SWFRect
746 Button::getBounds() const
748 SWFRect allBounds;
750 typedef std::vector<const DisplayObject*> Chars;
751 Chars actChars;
752 getActiveCharacters(actChars);
753 for (Chars::const_iterator i = actChars.begin(), e = actChars.end();
754 i != e; ++i) {
756 const DisplayObject* ch = *i;
757 // Child bounds need be transformed in our coordinate space
758 SWFRect lclBounds = ch->getBounds();
759 SWFMatrix m = getMatrix(*ch);
760 allBounds.expand_to_transformed_rect(m, lclBounds);
763 return allBounds;
766 bool
767 Button::pointInShape(boost::int32_t x, boost::int32_t y) const
769 typedef std::vector<const DisplayObject*> Chars;
770 Chars actChars;
771 getActiveCharacters(actChars);
772 for(Chars::const_iterator i=actChars.begin(),e=actChars.end(); i!=e; ++i)
774 const DisplayObject* ch = *i;
775 if (ch->pointInShape(x,y)) return true;
777 return false;
780 void
781 Button::construct(as_object* initObj)
783 // This can happen if attachMovie is called with an exported Button and
784 // an init object. The attachment happens, but the init object is not used
785 // (see misc-ming.all/attachMovieTest.swf).
786 if (initObj) {
787 IF_VERBOSE_ASCODING_ERRORS(
788 log_aserror(_("Button placed with an init object. This will "
789 "be ignored."));
793 saveOriginalTarget(); // for soft refs
795 // Don't register this button instance as a live DisplayObject.
797 // Instantiate the hit DisplayObjects
798 ActiveRecords hitChars;
799 get_active_records(hitChars, MOUSESTATE_HIT);
800 for (ActiveRecords::iterator i=hitChars.begin(),e=hitChars.end(); i!=e; ++i)
802 const SWF::ButtonRecord& rec = _def->buttonRecords()[*i];
804 // These should not be named!
805 DisplayObject* ch = rec.instantiate(this, false);
806 _hitCharacters.push_back(ch);
809 // Setup the state DisplayObjects container
810 // It will have a slot for each DisplayObject record.
811 // Some slots will probably be never used (consider HIT-only records)
812 // but for now this direct corrispondence between record number
813 // and active DisplayObject will be handy.
814 _stateCharacters.resize(_def->buttonRecords().size());
816 // Instantiate the default state DisplayObjects
817 ActiveRecords upChars;
818 get_active_records(upChars, MOUSESTATE_UP);
820 for (ActiveRecords::iterator i = upChars.begin(), e=upChars.end();
821 i != e; ++i)
823 int rno = *i;
824 const SWF::ButtonRecord& rec = _def->buttonRecords()[rno];
826 DisplayObject* ch = rec.instantiate(this);
828 _stateCharacters[rno] = ch;
829 addInstanceProperty(*this, ch);
830 ch->construct();
833 // There is no INITIALIZE/CONSTRUCT/LOAD/ENTERFRAME/UNLOAD event
834 // for Buttons
836 // Register key events.
837 if (_def->hasKeyPressHandler()) {
838 stage().registerButton(this);
843 void
844 Button::markOwnResources() const
847 // Mark state DisplayObjects as reachable
848 for (DisplayObjects::const_iterator i = _stateCharacters.begin(),
849 e = _stateCharacters.end(); i != e; ++i)
851 DisplayObject* ch = *i;
852 if (ch) ch->setReachable();
855 // Mark hit DisplayObjects as reachable
856 std::for_each(_hitCharacters.begin(), _hitCharacters.end(),
857 std::mem_fun(&DisplayObject::setReachable));
861 bool
862 Button::unloadChildren()
864 bool childsHaveUnload = false;
866 // We need to unload all children, or the global instance list
867 // will keep growing forever !
868 for (DisplayObjects::iterator i = _stateCharacters.begin(),
869 e = _stateCharacters.end(); i != e; ++i)
871 DisplayObject* ch = *i;
872 if (!ch || ch->unloaded()) continue;
873 if (ch->unload()) childsHaveUnload = true;
876 // NOTE: we don't need to ::unload or ::destroy here
877 // as the _hitCharacters are never placed on stage.
878 // As an optimization we might not even instantiate
879 // them, and only use the definition and the
880 // associated transform SWFMatrix... (would take
881 // hit instance off the GC).
882 _hitCharacters.clear();
884 return childsHaveUnload;
887 void
888 Button::destroy()
890 stage().removeButton(this);
892 for (DisplayObjects::iterator i = _stateCharacters.begin(),
893 e=_stateCharacters.end(); i != e; ++i) {
894 DisplayObject* ch = *i;
895 if (!ch || ch->isDestroyed()) continue;
896 ch->destroy();
899 // NOTE: we don't need to ::unload or ::destroy here
900 // as the _hitCharacters are never placed on stage.
901 // As an optimization we might not even instantiate
902 // them, and only use the definition and the
903 // associated transform SWFMatrix... (would take
904 // hit instance off the GC).
905 _hitCharacters.clear();
907 DisplayObject::destroy();
911 Button::getDefinitionVersion() const
913 return _def->getSWFVersion();
916 void
917 button_class_init(as_object& global, const ObjectURI& uri)
919 // This is going to be the global Button "class"/"function"
920 Global_as& gl = getGlobal(global);
921 as_object* proto = createObject(gl);
922 as_object* cl = gl.createClass(emptyFunction, proto);
923 attachButtonInterface(*proto);
925 // Register _global.MovieClip
926 global.init_member(uri, cl, as_object::DefaultFlags);
929 void
930 registerButtonNative(as_object& global)
932 VM& vm = getVM(global);
933 vm.registerNative(button_setTabIndex, 105, 1);
934 vm.registerNative(button_getTabIndex, 105, 2);
935 vm.registerNative(button_getDepth, 105, 3);
936 vm.registerNative(button_scale9Grid, 105, 4);
937 vm.registerNative(button_filters, 105, 5);
938 vm.registerNative(button_cacheAsBitmap, 105, 6);
939 vm.registerNative(button_blendMode, 105, 7);
942 #ifdef USE_SWFTREE
943 DisplayObject::InfoTree::iterator
944 Button::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
946 InfoTree::iterator selfIt = DisplayObject::getMovieInfo(tr, it);
947 std::ostringstream os;
949 DisplayObjects actChars;
950 getActiveCharacters(actChars, true);
951 std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
953 os.str("");
954 os << std::boolalpha << isEnabled();
955 InfoTree::iterator localIter = tr.append_child(selfIt,
956 std::make_pair(_("Enabled"), os.str()));
958 os.str("");
959 os << _mouseState;
960 localIter = tr.append_child(selfIt,
961 std::make_pair(_("Button state"), os.str()));
963 os.str("");
964 os << actChars.size();
965 localIter = tr.append_child(selfIt, std::make_pair(_("Action characters"),
966 os.str()));
968 std::for_each(actChars.begin(), actChars.end(),
969 boost::bind(&DisplayObject::getMovieInfo, _1, tr, localIter));
971 return selfIt;
974 #endif
976 std::ostream&
977 operator<<(std::ostream& o, const Button::MouseState& st)
979 switch (st) {
980 case Button::MOUSESTATE_UP: return o << "UP";
981 case Button::MOUSESTATE_DOWN: return o << "DOWN";
982 case Button::MOUSESTATE_OVER: return o << "OVER";
983 case Button::MOUSESTATE_HIT: return o << "HIT";
984 default: return o << "Unknown state";
988 namespace {
990 as_value
991 button_blendMode(const fn_call& fn)
993 Button* obj = ensure<IsDisplayObject<Button> >(fn);
994 LOG_ONCE(log_unimpl(_("Button.blendMode")));
995 UNUSED(obj);
996 return as_value();
999 as_value
1000 button_cacheAsBitmap(const fn_call& fn)
1002 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1003 LOG_ONCE(log_unimpl(_("Button.cacheAsBitmap")));
1004 UNUSED(obj);
1005 return as_value();
1008 as_value
1009 button_filters(const fn_call& fn)
1011 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1012 LOG_ONCE(log_unimpl(_("Button.filters")));
1013 UNUSED(obj);
1014 return as_value();
1017 as_value
1018 button_scale9Grid(const fn_call& fn)
1020 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1021 LOG_ONCE(log_unimpl(_("Button.scale9Grid")));
1022 UNUSED(obj);
1023 return as_value();
1026 as_value
1027 button_getTabIndex(const fn_call& fn)
1029 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1030 LOG_ONCE(log_unimpl(_("Button.getTabIndex")));
1031 UNUSED(obj);
1032 return as_value();
1035 as_value
1036 button_setTabIndex(const fn_call& fn)
1038 Button* obj = ensure<IsDisplayObject<Button> >(fn);
1039 LOG_ONCE(log_unimpl(_("Button.setTabIndex")));
1040 UNUSED(obj);
1041 return as_value();
1044 as_value
1045 button_getDepth(const fn_call& fn)
1047 // This does exactly the same as MovieClip.getDepth, but appears to be
1048 // a separate function.
1049 DisplayObject* obj = ensure<IsDisplayObject<Button> >(fn);
1050 return as_value(obj->get_depth());
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: nil
1062 // End: