1 // Button.cpp:  Mouse-sensitive buttons, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   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.
15 //
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
19 //
20 
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWF_TREE
23 #endif
24 
25 #include "Button.h"
26 
27 #include <functional>
28 #include <utility>
29 #include <functional>
30 
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"
49 #include "sound_handler.h"
50 
51 /** \page buttons Buttons and mouse behaviour
52 
53 Observations about button & mouse behavior
54 
55 Entities that receive mouse events: only buttons and sprites, AFAIK
56 
57 When the mouse button goes down, it becomes "captured" by whatever
58 element is topmost, directly below the mouse at that moment.  While
59 the mouse is captured, no other entity receives mouse events,
60 regardless of how the mouse or other elements move.
61 
62 The mouse remains captured until the mouse button goes up.  The mouse
63 remains captured even if the element that captured it is removed from
64 the display list.
65 
66 If the mouse isn't above a button or sprite when the mouse button goes
67 down, then the mouse is captured by the background (i.e. mouse events
68 just don't get sent, until the mouse button goes up again).
69 
70 Mouse events:
71 
72 +------------------+---------------+-------------------------------------+
73 | Event            | Mouse Button  | description                         |
74 =========================================================================
75 | onRollOver       |     up        | sent to topmost entity when mouse   |
76 |                  |               | cursor initially goes over it       |
77 +------------------+---------------+-------------------------------------+
78 | onRollOut        |     up        | when mouse leaves entity, after     |
79 |                  |               | onRollOver                          |
80 +------------------+---------------+-------------------------------------+
81 | onPress          |  up -> down   | sent to topmost entity when mouse   |
82 |                  |               | button goes down.  onRollOver       |
83 |                  |               | always precedes onPress.  Initiates |
84 |                  |               | mouse capture.                      |
85 +------------------+---------------+-------------------------------------+
86 | onRelease        |  down -> up   | sent to active entity if mouse goes |
87 |                  |               | up while over the element           |
88 +------------------+---------------+-------------------------------------+
89 | onDragOut        |     down      | sent to active entity if mouse      |
90 |                  |               | is no longer over the entity        |
91 +------------------+---------------+-------------------------------------+
92 | onReleaseOutside |  down -> up   | sent to active entity if mouse goes |
93 |                  |               | up while not over the entity.       |
94 |                  |               | onDragOut always precedes           |
95 |                  |               | onReleaseOutside                    |
96 +------------------+---------------+-------------------------------------+
97 | onDragOver       |     down      | sent to active entity if mouse is   |
98 |                  |               | dragged back over it after          |
99 |                  |               | onDragOut                           |
100 +------------------+---------------+-------------------------------------+
101 
102 There is always one active entity at any given time (considering NULL to
103 be an active entity, representing the background, and other objects that
104 don't receive mouse events).
105 
106 When the mouse button is up, the active entity is the topmost element
107 directly under the mouse pointer.
108 
109 When the mouse button is down, the active entity remains whatever it
110 was when the button last went down.
111 
112 The active entity is the only object that receives mouse events.
113 
114 !!! The "trackAsMenu" property alters this behavior!  If trackAsMenu
115 is set on the active entity, then onReleaseOutside is filtered out,
116 and onDragOver from another entity is allowed (from the background, or
117 another trackAsMenu entity). !!!
118 
119 
120 Pseudocode:
121 
122 active_entity = NULL
123 mouse_button_state = UP
124 mouse_inside_entity_state = false
125 frame loop:
126   if mouse_button_state == DOWN
127 
128     // Handle trackAsMenu
129     if (active_entity->trackAsMenu)
130       possible_entity = topmost entity below mouse
131       if (possible_entity != active_entity && possible_entity->trackAsMenu)
132         // Transfer to possible entity
133     active_entity = possible_entity
134     active_entity->onDragOver()
135     mouse_inside_entity_state = true;
136 
137     // Handle onDragOut, onDragOver
138     if (mouse_inside_entity_state == false)
139       if (mouse is actually inside the active_entity)
140         // onDragOver
141     active_entity->onDragOver()
142         mouse_inside_entity_state = true;
143 
144     else // mouse_inside_entity_state == true
145       if (mouse is actually outside the active_entity)
146         // onDragOut
147     active_entity->onDragOut()
148     mouse_inside_entity_state = false;
149 
150     // Handle onRelease, onReleaseOutside
151     if (mouse button is up)
152       if (mouse_inside_entity_state)
153         // onRelease
154         active_entity->onRelease()
155       else
156         // onReleaseOutside
157     if (active_entity->trackAsMenu == false)
158           active_entity->onReleaseOutside()
159       mouse_button_state = UP
160 
161   if mouse_button_state == UP
162     new_active_entity = topmost entity below the mouse
163     if (new_active_entity != active_entity)
164       // onRollOut, onRollOver
165       active_entity->onRollOut()
166       active_entity = new_active_entity
167       active_entity->onRollOver()
168 
169     // Handle press
170     if (mouse button is down)
171       // onPress
172       active_entity->onPress()
173       mouse_inside_entity_state = true
174       mouse_button_state = DOWN
175 
176 */
177 
178 
179 namespace gnash {
180 
181 namespace {
182     as_value button_blendMode(const fn_call& fn);
183     as_value button_cacheAsBitmap(const fn_call& fn);
184     as_value button_filters(const fn_call& fn);
185     as_value button_scale9Grid(const fn_call& fn);
186     as_value button_setTabIndex(const fn_call& fn);
187     as_value button_getTabIndex(const fn_call& fn);
188     as_value button_getDepth(const fn_call& fn);
189 }
190 
191 namespace {
192 
193 class ButtonActionExecutor {
194 public:
ButtonActionExecutor(as_environment & env)195     ButtonActionExecutor(as_environment& env)
196         :
197         _env(env)
198     {}
199 
operator ()(const action_buffer & ab)200     void operator() (const action_buffer& ab)
201     {
202         ActionExec exec(ab, _env);
203         exec();
204     }
205 private:
206     as_environment& _env;
207 };
208 
209 class ButtonActionPusher
210 {
211 public:
ButtonActionPusher(movie_root & mr,DisplayObject * this_ptr)212     ButtonActionPusher(movie_root& mr, DisplayObject* this_ptr)
213         :
214         _mr(mr),
215         _tp(this_ptr)
216     {}
217 
operator ()(const action_buffer & ab)218     void operator()(const action_buffer& ab)
219     {
220         _mr.pushAction(ab, _tp);
221     }
222 
223 private:
224     movie_root& _mr;
225     DisplayObject* _tp;
226 };
227 
228 }
229 
230 namespace {
addInstanceProperty(Button & b,DisplayObject * d)231     void addInstanceProperty(Button& b, DisplayObject* d) {
232         if (!d) return;
233         const ObjectURI& name = d->get_name();
234         if (name.empty()) return;
235         getObject(&b)->init_member(name, getObject(d), 0);
236     }
237 
removeInstanceProperty(Button & b,DisplayObject * d)238     void removeInstanceProperty(Button& b, DisplayObject* d) {
239         if (!d) return;
240         const ObjectURI& name = d->get_name();
241         if (name.empty()) return;
242         getObject(&b)->delProperty(name);
243     }
244 }
245 
246 /// Predicates for standard algorithms.
247 
248 /// Depth comparator for DisplayObjects.
charDepthLessThen(const DisplayObject * ch1,const DisplayObject * ch2)249 static bool charDepthLessThen(const DisplayObject* ch1, const DisplayObject* ch2)
250 {
251     return ch1->get_depth() < ch2->get_depth();
252 }
253 
254 /// Predicate for finding active DisplayObjects.
255 //
256 /// Returns true if the DisplayObject should be skipped:
257 /// 1) if it is NULL, or
258 /// 2) if we don't want unloaded DisplayObjects and the DisplayObject is unloaded.
isCharacterNull(DisplayObject * ch,bool includeUnloaded)259 static bool isCharacterNull(DisplayObject* ch, bool includeUnloaded)
260 {
261     return (!ch || (!includeUnloaded && ch->unloaded()));
262 }
263 
264 static void
attachButtonInterface(as_object & o)265 attachButtonInterface(as_object& o)
266 {
267 
268     const int unprotected = 0;
269     o.init_member(NSV::PROP_ENABLED, true, unprotected);
270     o.init_member("useHandCursor", true, unprotected);
271 
272     const int swf8Flags = PropFlags::onlySWF8Up;
273     VM& vm = getVM(o);
274 
275     o.init_property("tabIndex", *vm.getNative(105, 1), *vm.getNative(105, 2),
276             swf8Flags);
277 
278     o.init_member("getDepth", vm.getNative(105, 3), unprotected);
279 
280     NativeFunction* gs;
281     gs = vm.getNative(105, 4);
282     o.init_property("scale9Grid", *gs, *gs, swf8Flags);
283     gs = vm.getNative(105, 5);
284     o.init_property("filters", *gs, *gs, swf8Flags);
285     gs = vm.getNative(105, 6);
286     o.init_property("cacheAsBitmap", *gs, *gs, swf8Flags);
287     gs = vm.getNative(105, 7);
288     o.init_property("blendMode", *gs, *gs, swf8Flags);
289 
290 }
291 
Button(as_object * object,const SWF::DefineButtonTag * def,DisplayObject * parent)292 Button::Button(as_object* object, const SWF::DefineButtonTag* def,
293         DisplayObject* parent)
294     :
295     InteractiveObject(object, parent),
296     _mouseState(MOUSESTATE_UP),
297     _def(def)
298 {
299     assert(object);
300 }
301 
~Button()302 Button::~Button()
303 {
304 }
305 
306 bool
trackAsMenu()307 Button::trackAsMenu()
308 {
309     // TODO: check whether the AS or the tag value takes precedence.
310     as_object* obj = getObject(this);
311     assert(obj);
312 
313     VM& vm = getVM(*obj);
314 
315     as_value track;
316     // TODO: use NSV
317     const ObjectURI& propTrackAsMenu = getURI(vm, "trackAsMenu");
318     if (obj->get_member(propTrackAsMenu, &track)) {
319         return toBool(track, vm);
320     }
321     if (_def) return _def->trackAsMenu();
322     return false;
323 }
324 
325 bool
isEnabled()326 Button::isEnabled()
327 {
328     as_object* obj = getObject(this);
329     assert(obj);
330 
331     as_value enabled;
332     if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) return false;
333 
334     return toBool(enabled, getVM(*obj));
335 }
336 
337 
338 void
keyPress(key::code c)339 Button::keyPress(key::code c)
340 {
341     if (unloaded()) {
342         // We don't respond to events while unloaded
343         // See bug #22982
344         return;
345     }
346 
347     ButtonActionPusher xec(stage(), this);
348     _def->forEachTrigger(event_id(event_id::KEY_PRESS, c), xec);
349 }
350 
351 bool
handleFocus()352 Button::handleFocus()
353 {
354     /// Nothing to do, but can receive focus.
355     return false;
356 }
357 
358 
359 void
display(Renderer & renderer,const Transform & base)360 Button::display(Renderer& renderer, const Transform& base)
361 {
362     const DisplayObject::MaskRenderer mr(renderer, *this);
363 
364     const Transform xform = base * transform();
365 
366     DisplayObjects actChars;
367     getActiveCharacters(actChars);
368 
369     // TODO: by keeping chars sorted by depth we'd avoid the sort on display
370     std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
371 
372     for (auto& actChar : actChars) {
373         actChar->display(renderer, xform);
374     }
375 
376     clear_invalidated();
377 }
378 
379 
380 // Return the topmost entity that the given point covers.  NULL if none.
381 // I.e. check against ourself.
382 InteractiveObject*
topmostMouseEntity(std::int32_t x,std::int32_t y)383 Button::topmostMouseEntity(std::int32_t x, std::int32_t y)
384 {
385     if (!visible() || !isEnabled())
386     {
387         return nullptr;
388     }
389 
390     //-------------------------------------------------
391     // Check our active and visible children first
392     //-------------------------------------------------
393 
394     DisplayObjects actChars;
395     getActiveCharacters(actChars);
396 
397     if ( ! actChars.empty() )
398     {
399         std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
400 
401         SWFMatrix m = getMatrix(*this);
402         point  p(x, y);
403         m.invert().transform(p);
404 
405         for (DisplayObjects::reverse_iterator it = actChars.rbegin(),
406                 itE=actChars.rend(); it!=itE; ++it)
407         {
408             DisplayObject* ch = *it;
409             if ( ! ch->visible() ) continue;
410             InteractiveObject *hit = ch->topmostMouseEntity(p.x, p.y);
411             if ( hit ) return hit;
412         }
413     }
414 
415     //-------------------------------------------------
416     // If that failed, check our hit area
417     //-------------------------------------------------
418 
419     // Find hit DisplayObjects
420     if ( _hitCharacters.empty() ) return nullptr;
421 
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);
428     }
429 
430     for (DisplayObjects::const_iterator i = _hitCharacters.begin(),
431          e = _hitCharacters.end(); i !=e; ++i)
432     {
433         if ((*i)->pointInVisibleShape(wp.x, wp.y))
434         {
435             // The mouse is inside the shape.
436             return this;
437         }
438     }
439 
440     return nullptr;
441 }
442 
443 
444 void
mouseEvent(const event_id & event)445 Button::mouseEvent(const event_id& event)
446 {
447     if (unloaded()) {
448         // We don't respond to events while unloaded. See bug #22982.
449         return;
450     }
451 
452     MouseState new_state = _mouseState;
453 
454     // Set our mouse state (so we know how to render).
455     switch (event.id())
456     {
457         case event_id::ROLL_OUT:
458         case event_id::RELEASE_OUTSIDE:
459             new_state = MOUSESTATE_UP;
460             break;
461 
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;
468 
469         case event_id::PRESS:
470         case event_id::DRAG_OVER:
471         case event_id::MOUSE_DOWN:
472             new_state = MOUSESTATE_DOWN;
473             break;
474 
475         default:
476             //abort();  // missed a case?
477             log_error(_("Unhandled button event %s"), event);
478             break;
479     }
480 
481     set_current_state(new_state);
482 
483     // Button transition sounds.
484     do {
485 
486         if (!_def->hasSound()) break;
487 
488         // Check if there is a sound handler
489         sound::sound_handler* s = getRunResources(*getObject(this)).soundHandler();
490         if (!s) break;
491 
492         int bi; // button sound array index [0..3]
493 
494         switch (event.id())
495         {
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;
511         }
512 
513         // no sound for this transition
514         if (bi < 0) break;
515 #ifdef USE_SOUND
516         const SWF::DefineButtonSoundTag::ButtonSound& bs =
517             _def->buttonSound(bi);
518 
519         // character zero is considered as null character
520         if (!bs.soundID) break;
521 
522         // No actual sound ?
523         if (!bs.sample) break;
524 
525         if (bs.soundInfo.stopPlayback) {
526             s->stopEventSound(bs.sample->m_sound_handler_id);
527         }
528         else {
529             const SWF::SoundInfoRecord& sinfo = bs.soundInfo;
530 
531             const sound::SoundEnvelopes* env =
532                 sinfo.envelopes.empty() ? nullptr : &sinfo.envelopes;
533 
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
540                     );
541         }
542 #endif  // USE_SOUND
543 
544     } while(0);
545 
546     // From: "ActionScript - The Definitive Guide" by Colin Moock
547     // (chapter 10: Events and Event Handlers)
548     //
549     // "Event-based code [..] is said to be executed asynchronously
550     //  because the triggering of events can occur at arbitrary times."
551     //
552     // We'll push to the global list. The movie_root will process
553     // the action queue on mouse event.
554     //
555 
556     movie_root& mr = stage();
557 
558     ButtonActionPusher xec(mr, this);
559     _def->forEachTrigger(event, xec);
560 
561     // check for built-in event handler.
562     std::unique_ptr<ExecutableCode> code (get_event_handler(event));
563     if (code.get()) {
564         mr.pushAction(std::move(code), movie_root::PRIORITY_DOACTION);
565     }
566 
567     sendEvent(*getObject(this), get_environment(), event.functionURI());
568 }
569 
570 
571 void
getActiveCharacters(ConstDisplayObjects & list) const572 Button::getActiveCharacters(ConstDisplayObjects& list) const
573 {
574     list.clear();
575 
576     // Copy all the DisplayObjects to the new list, skipping NULL and unloaded
577     // DisplayObjects.
578     std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
579             std::back_inserter(list),
580             std::bind(&isCharacterNull, std::placeholders::_1, false));
581 
582 }
583 
584 
585 void
getActiveCharacters(DisplayObjects & list,bool includeUnloaded)586 Button::getActiveCharacters(DisplayObjects& list, bool includeUnloaded)
587 {
588     list.clear();
589 
590     // Copy all the DisplayObjects to the new list, skipping NULL
591     // DisplayObjects, optionally including unloaded DisplayObjects.
592     std::remove_copy_if(_stateCharacters.begin(), _stateCharacters.end(),
593             std::back_inserter(list),
594             std::bind(&isCharacterNull, std::placeholders::_1, includeUnloaded));
595 
596 }
597 
598 void
get_active_records(ActiveRecords & list,MouseState state)599 Button::get_active_records(ActiveRecords& list, MouseState state)
600 {
601     list.clear();
602 
603     using namespace SWF;
604     const DefineButtonTag::ButtonRecords& br = _def->buttonRecords();
605     size_t index = 0;
606 
607     for (DefineButtonTag::ButtonRecords::const_iterator i = br.begin(),
608             e = br.end(); i != e; ++i, ++index)
609     {
610         const ButtonRecord& rec =*i;
611         if (rec.hasState(state)) list.insert(index);
612     }
613 }
614 
615 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
dump(Button::DisplayObjects & chars,std::stringstream & ss)616 static void dump(Button::DisplayObjects& chars, std::stringstream& ss)
617 {
618     for (size_t i=0, e=chars.size(); i<e; ++i)
619     {
620         ss << "Record" << i << ": ";
621         DisplayObject* ch = chars[i];
622         if ( ! ch ) ss << "NULL.";
623         else
624         {
625             ss << ch->getTarget() << " (depth:" <<
626                 ch->get_depth()-DisplayObject::staticDepthOffset-1
627                 << " unloaded:" << ch->unloaded() <<
628                 " destroyed:" << ch->isDestroyed() << ")";
629         }
630         ss << std::endl;
631     }
632 }
633 #endif
634 
635 void
set_current_state(MouseState new_state)636 Button::set_current_state(MouseState new_state)
637 {
638     if (new_state == _mouseState)
639         return;
640 
641 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
642     std::stringstream ss;
643     ss << "at set_current_state enter: " << std::endl;
644     dump(_stateCharacters, ss);
645     log_debug("%s", ss.str());
646 #endif
647 
648     // Get new state records
649     ActiveRecords newChars;
650     get_active_records(newChars, new_state);
651 
652     // For each possible record, check if it should still be there
653     for (size_t i=0, e=_stateCharacters.size(); i<e; ++i)
654     {
655         DisplayObject* oldch = _stateCharacters[i];
656         bool shouldBeThere = ( newChars.find(i) != newChars.end() );
657 
658         if ( ! shouldBeThere )
659         {
660 
661             // is there, but is unloaded: destroy, clear slot and go on
662             if ( oldch && oldch->unloaded() ) {
663                 removeInstanceProperty(*this, oldch);
664                 if ( ! oldch->isDestroyed() ) oldch->destroy();
665                 _stateCharacters[i] = nullptr;
666                 oldch = nullptr;
667             }
668 
669             if ( oldch ) // the one we have should not be there... unload!
670             {
671                 set_invalidated();
672 
673                 if ( ! oldch->unload() )
674                 {
675                     // No onUnload handler: destroy and clear slot
676                     removeInstanceProperty(*this, oldch);
677                     if (!oldch->isDestroyed()) oldch->destroy();
678                     _stateCharacters[i] = nullptr;
679                 }
680                 else
681                 {
682                     // onUnload handler: shift depth and keep slot
683                     int oldDepth = oldch->get_depth();
684                     int newDepth = DisplayObject::removedDepthOffset - oldDepth;
685 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
686                     log_debug("Removed button record shifted from depth %d to depth %d",
687 			      oldDepth, newDepth);
688 #endif
689                     oldch->set_depth(newDepth);
690                 }
691             }
692         }
693         else // should be there
694         {
695             // Is there already, but is unloaded: destroy and consider as gone
696             if ( oldch && oldch->unloaded() )
697             {
698                 removeInstanceProperty(*this, oldch);
699                 if ( ! oldch->isDestroyed() ) oldch->destroy();
700                 _stateCharacters[i] = nullptr;
701                 oldch = nullptr;
702             }
703 
704             if (!oldch) {
705                 // Not there, instantiate
706                 const SWF::ButtonRecord& rec = _def->buttonRecords()[i];
707                 DisplayObject* ch = rec.instantiate(this);
708 
709                 set_invalidated();
710                 _stateCharacters[i] = ch;
711                 addInstanceProperty(*this, ch);
712                 ch->construct();
713             }
714         }
715     }
716 
717 #ifdef GNASH_DEBUG_BUTTON_DISPLAYLIST
718     ss.str("");
719     ss << "at set_current_state end: " << std::endl;
720     dump(_stateCharacters, ss);
721     log_debug("%s", ss.str());
722 #endif
723 
724     // Remember current state
725     _mouseState = new_state;
726 
727 }
728 
729 void
add_invalidated_bounds(InvalidatedRanges & ranges,bool force)730 Button::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
731 {
732 
733     // Not visible anyway
734     if (!visible()) return;
735 
736     ranges.add(m_old_invalidated_ranges);
737 
738     DisplayObjects actChars;
739     getActiveCharacters(actChars);
740     std::for_each(actChars.begin(), actChars.end(),
741             std::bind(&DisplayObject::add_invalidated_bounds, std::placeholders::_1,
742                 std::ref(ranges), force || invalidated())
743     );
744 }
745 
746 SWFRect
getBounds() const747 Button::getBounds() const
748 {
749     SWFRect allBounds;
750 
751     typedef std::vector<const DisplayObject*> Chars;
752     Chars actChars;
753     getActiveCharacters(actChars);
754     for (Chars::const_iterator i = actChars.begin(), e = actChars.end();
755             i != e; ++i) {
756 
757         const DisplayObject* ch = *i;
758         // Child bounds need be transformed in our coordinate space
759         SWFRect lclBounds = ch->getBounds();
760         SWFMatrix m = getMatrix(*ch);
761         allBounds.expand_to_transformed_rect(m, lclBounds);
762     }
763 
764     return allBounds;
765 }
766 
767 bool
pointInShape(std::int32_t x,std::int32_t y) const768 Button::pointInShape(std::int32_t x, std::int32_t y) const
769 {
770     typedef std::vector<const DisplayObject*> Chars;
771     Chars actChars;
772     getActiveCharacters(actChars);
773     for(Chars::const_iterator i=actChars.begin(),e=actChars.end(); i!=e; ++i)
774     {
775         const DisplayObject* ch = *i;
776         if (ch->pointInShape(x,y)) return true;
777     }
778     return false;
779 }
780 
781 void
construct(as_object * initObj)782 Button::construct(as_object* initObj)
783 {
784     // This can happen if attachMovie is called with an exported Button and
785     // an init object. The attachment happens, but the init object is not used
786     // (see misc-ming.all/attachMovieTest.swf).
787     if (initObj) {
788         IF_VERBOSE_ASCODING_ERRORS(
789 		log_aserror(_("Button placed with an init object. This will "
790 			      "be ignored."));
791         );
792     }
793 
794     saveOriginalTarget(); // for soft refs
795 
796     // Don't register this button instance as a live DisplayObject.
797 
798     // Instantiate the hit DisplayObjects
799     ActiveRecords hitChars;
800     get_active_records(hitChars, MOUSESTATE_HIT);
801     for (const auto& hitChar : hitChars)
802     {
803         const SWF::ButtonRecord& rec = _def->buttonRecords()[hitChar];
804 
805         // These should not be named!
806         DisplayObject* ch = rec.instantiate(this, false);
807         _hitCharacters.push_back(ch);
808     }
809 
810     // Setup the state DisplayObjects container
811     // It will have a slot for each DisplayObject record.
812     // Some slots will probably be never used (consider HIT-only records)
813     // but for now this direct corrispondence between record number
814     // and active DisplayObject will be handy.
815     _stateCharacters.resize(_def->buttonRecords().size());
816 
817     // Instantiate the default state DisplayObjects
818     ActiveRecords upChars;
819     get_active_records(upChars, MOUSESTATE_UP);
820 
821     for (auto rno : upChars)
822     {
823         const SWF::ButtonRecord& rec = _def->buttonRecords()[rno];
824 
825         DisplayObject* ch = rec.instantiate(this);
826 
827         _stateCharacters[rno] = ch;
828         addInstanceProperty(*this, ch);
829         ch->construct();
830     }
831 
832     // There is no INITIALIZE/CONSTRUCT/LOAD/ENTERFRAME/UNLOAD event
833     // for Buttons
834 
835     // Register key events.
836     if (_def->hasKeyPressHandler()) {
837         stage().registerButton(this);
838     }
839 
840 }
841 
842 void
markOwnResources() const843 Button::markOwnResources() const
844 {
845 
846     // Mark state DisplayObjects as reachable
847     for (DisplayObject* ch : _stateCharacters)
848     {
849         if (ch) ch->setReachable();
850     }
851 
852     // Mark hit DisplayObjects as reachable
853     std::for_each(_hitCharacters.begin(), _hitCharacters.end(),
854             std::mem_fun(&DisplayObject::setReachable));
855 
856 }
857 
858 bool
unloadChildren()859 Button::unloadChildren()
860 {
861     bool childsHaveUnload = false;
862 
863     // We need to unload all children, or the global instance list
864     // will keep growing forever !
865     for (DisplayObject* ch : _stateCharacters)
866     {
867         if (!ch || ch->unloaded()) continue;
868         if (ch->unload()) childsHaveUnload = true;
869     }
870 
871     // NOTE: we don't need to ::unload or ::destroy here
872     //       as the _hitCharacters are never placed on stage.
873     //       As an optimization we might not even instantiate
874     //       them, and only use the definition and the
875     //       associated transform SWFMatrix... (would take
876     //       hit instance off the GC).
877     _hitCharacters.clear();
878 
879     return childsHaveUnload;
880 }
881 
882 void
destroy()883 Button::destroy()
884 {
885     stage().removeButton(this);
886 
887     for (DisplayObject* ch : _stateCharacters) {
888         if (!ch || ch->isDestroyed()) continue;
889         ch->destroy();
890     }
891 
892     // NOTE: we don't need to ::unload or ::destroy here
893     //       as the _hitCharacters are never placed on stage.
894     //       As an optimization we might not even instantiate
895     //       them, and only use the definition and the
896     //       associated transform SWFMatrix... (would take
897     //       hit instance off the GC).
898     _hitCharacters.clear();
899 
900     DisplayObject::destroy();
901 }
902 
903 int
getDefinitionVersion() const904 Button::getDefinitionVersion() const
905 {
906     return _def->getSWFVersion();
907 }
908 
909 void
button_class_init(as_object & global,const ObjectURI & uri)910 button_class_init(as_object& global, const ObjectURI& uri)
911 {
912     // This is going to be the global Button "class"/"function"
913     Global_as& gl = getGlobal(global);
914     as_object* proto = createObject(gl);
915     as_object* cl = gl.createClass(emptyFunction, proto);
916     attachButtonInterface(*proto);
917 
918     // Register _global.MovieClip
919     global.init_member(uri, cl, as_object::DefaultFlags);
920 }
921 
922 void
registerButtonNative(as_object & global)923 registerButtonNative(as_object& global)
924 {
925     VM& vm = getVM(global);
926     vm.registerNative(button_setTabIndex, 105, 1);
927     vm.registerNative(button_getTabIndex, 105, 2);
928     vm.registerNative(button_getDepth, 105, 3);
929     vm.registerNative(button_scale9Grid, 105, 4);
930     vm.registerNative(button_filters, 105, 5);
931     vm.registerNative(button_cacheAsBitmap, 105, 6);
932     vm.registerNative(button_blendMode, 105, 7);
933 }
934 
935 #ifdef USE_SWFTREE
936 DisplayObject::InfoTree::iterator
getMovieInfo(InfoTree & tr,InfoTree::iterator it)937 Button::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
938 {
939     InfoTree::iterator selfIt = DisplayObject::getMovieInfo(tr, it);
940     std::ostringstream os;
941 
942     DisplayObjects actChars;
943     getActiveCharacters(actChars, true);
944     std::sort(actChars.begin(), actChars.end(), charDepthLessThen);
945 
946     os.str("");
947     os << std::boolalpha << isEnabled();
948     InfoTree::iterator localIter = tr.append_child(selfIt,
949             std::make_pair(_("Enabled"), os.str()));
950 
951     os.str("");
952     os << _mouseState;
953     localIter = tr.append_child(selfIt,
954             std::make_pair(_("Button state"), os.str()));
955 
956     os.str("");
957     os << actChars.size();
958     localIter = tr.append_child(selfIt, std::make_pair(_("Action characters"),
959             os.str()));
960 
961     std::for_each(actChars.begin(), actChars.end(),
962             std::bind(&DisplayObject::getMovieInfo, std::placeholders::_1, tr, localIter));
963 
964     return selfIt;
965 
966 }
967 #endif
968 
969 std::ostream&
operator <<(std::ostream & o,const Button::MouseState & st)970 operator<<(std::ostream& o, const Button::MouseState& st)
971 {
972     switch (st) {
973         case Button::MOUSESTATE_UP: return o << "UP";
974         case Button::MOUSESTATE_DOWN: return o << "DOWN";
975         case Button::MOUSESTATE_OVER: return o << "OVER";
976         case Button::MOUSESTATE_HIT: return o << "HIT";
977         default: return o << "Unknown state";
978     }
979 }
980 
981 namespace {
982 
983 as_value
button_blendMode(const fn_call & fn)984 button_blendMode(const fn_call& fn)
985 {
986     Button* obj = ensure<IsDisplayObject<Button> >(fn);
987     LOG_ONCE(log_unimpl(_("Button.blendMode")));
988     UNUSED(obj);
989     return as_value();
990 }
991 
992 as_value
button_cacheAsBitmap(const fn_call & fn)993 button_cacheAsBitmap(const fn_call& fn)
994 {
995     Button* obj = ensure<IsDisplayObject<Button> >(fn);
996     LOG_ONCE(log_unimpl(_("Button.cacheAsBitmap")));
997     UNUSED(obj);
998     return as_value();
999 }
1000 
1001 as_value
button_filters(const fn_call & fn)1002 button_filters(const fn_call& fn)
1003 {
1004     Button* obj = ensure<IsDisplayObject<Button> >(fn);
1005     LOG_ONCE(log_unimpl(_("Button.filters")));
1006     UNUSED(obj);
1007     return as_value();
1008 }
1009 
1010 as_value
button_scale9Grid(const fn_call & fn)1011 button_scale9Grid(const fn_call& fn)
1012 {
1013     Button* obj = ensure<IsDisplayObject<Button> >(fn);
1014     LOG_ONCE(log_unimpl(_("Button.scale9Grid")));
1015     UNUSED(obj);
1016     return as_value();
1017 }
1018 
1019 as_value
button_getTabIndex(const fn_call & fn)1020 button_getTabIndex(const fn_call& fn)
1021 {
1022     Button* obj = ensure<IsDisplayObject<Button> >(fn);
1023     LOG_ONCE(log_unimpl(_("Button.getTabIndex")));
1024     UNUSED(obj);
1025     return as_value();
1026 }
1027 
1028 as_value
button_setTabIndex(const fn_call & fn)1029 button_setTabIndex(const fn_call& fn)
1030 {
1031     Button* obj = ensure<IsDisplayObject<Button> >(fn);
1032     LOG_ONCE(log_unimpl(_("Button.setTabIndex")));
1033     UNUSED(obj);
1034     return as_value();
1035 }
1036 
1037 as_value
button_getDepth(const fn_call & fn)1038 button_getDepth(const fn_call& fn)
1039 {
1040     // This does exactly the same as MovieClip.getDepth, but appears to be
1041     // a separate function.
1042     DisplayObject* obj = ensure<IsDisplayObject<Button> >(fn);
1043     return as_value(obj->get_depth());
1044 }
1045 
1046 } // anonymous namespace
1047 } // end of namespace gnash
1048 
1049 
1050 // Local Variables:
1051 // mode: C++
1052 // c-basic-offset: 8
1053 // tab-width: 8
1054 // indent-tabs-mode: nil
1055 // End:
1056