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