1 // DefineButtonTag.cpp: Mouse-sensitive SWF buttons, for Gnash.
2 //
3 // Copyright (C) 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 #include "DefineButtonTag.h"
22
23 #include <string>
24 #include <boost/functional.hpp>
25
26 #include "TypesParser.h"
27 #include "RunResources.h"
28 #include "DisplayObject.h"
29 #include "Button.h" // for createDisplayObject()
30 #include "DefineButtonCxformTag.h"
31 #include "SWF.h"
32 #include "SWFStream.h" // for read()
33 #include "movie_definition.h"
34 #include "action_buffer.h"
35 #include "filter_factory.h"
36 #include "GnashKey.h" // for gnash::key::codeMap
37 #include "GnashAlgorithm.h"
38 #include "Global_as.h"
39 #include "namedStrings.h"
40 #include "as_function.h"
41
42 namespace gnash {
43 namespace SWF {
44
45 // Forward declarations
46 namespace {
47 std::string computeButtonStatesString(int flags);
48 }
49
DefineButtonTag(SWFStream & in,movie_definition & m,TagType tag,std::uint16_t id)50 DefineButtonTag::DefineButtonTag(SWFStream& in, movie_definition& m,
51 TagType tag, std::uint16_t id)
52 :
53 DefinitionTag(id),
54 _trackAsMenu(false),
55 _movieDef(m)
56 {
57 switch (tag) {
58 default:
59 std::abort();
60 break;
61 case DEFINEBUTTON:
62 readDefineButtonTag(in, m);
63 break;
64 case DEFINEBUTTON2:
65 readDefineButton2Tag(in, m);
66 break;
67 }
68 }
69
~DefineButtonTag()70 DefineButtonTag::~DefineButtonTag()
71 {
72 }
73
74
75 void
loader(SWFStream & in,TagType tag,movie_definition & m,const RunResources &)76 DefineButtonTag::loader(SWFStream& in, TagType tag, movie_definition& m,
77 const RunResources& /*r*/)
78 {
79 assert(tag == DEFINEBUTTON);
80 in.ensureBytes(2);
81 const std::uint16_t id = in.read_u16();
82
83 IF_VERBOSE_PARSE(
84 log_parse(_(" DefineButton loader: character id = %d"), id);
85 );
86
87 std::unique_ptr<DefineButtonTag> bt(new DefineButtonTag(in, m, tag, id));
88
89 m.addDisplayObject(id, bt.release());
90 }
91
92 void
loader(SWFStream & in,TagType tag,movie_definition & m,const RunResources &)93 DefineButton2Tag::loader(SWFStream& in, TagType tag, movie_definition& m,
94 const RunResources& /*r*/)
95 {
96 assert(tag == DEFINEBUTTON2);
97 in.ensureBytes(2);
98 const std::uint16_t id = in.read_u16();
99
100 IF_VERBOSE_PARSE(
101 log_parse(_(" DefineButton2 loader: chararacter id = %d"), id);
102 );
103
104 std::unique_ptr<DefineButtonTag> bt(new DefineButtonTag(in, m, tag, id));
105
106 m.addDisplayObject(id, bt.release());
107 }
108
109
110 void
readDefineButtonTag(SWFStream & in,movie_definition & m)111 DefineButtonTag::readDefineButtonTag(SWFStream& in, movie_definition& m)
112 {
113
114 // Old button tag.
115
116 unsigned long endTagPos = in.get_tag_end_position();
117
118 // Read button DisplayObject records.
119 for (;;) {
120 ButtonRecord r;
121 if (r.read(in, SWF::DEFINEBUTTON, m, endTagPos) == false) {
122 // Null record; marks the end of button records.
123 break;
124 }
125
126 // SAFETY CHECK:
127 // if the ButtonRecord is corrupted, discard it
128 if (r.valid()) _buttonRecords.push_back(std::move(r));
129 }
130
131 if (in.tell() >= endTagPos) {
132 IF_VERBOSE_MALFORMED_SWF(
133 log_swferror(_("Premature end of DEFINEBUTTON tag, "
134 "won't read actions"));
135 );
136 return;
137 }
138
139 // Read actions.
140 _buttonActions.push_back(new ButtonAction(in, SWF::DEFINEBUTTON,
141 endTagPos, m));
142
143 }
144
145 void
readDefineButton2Tag(SWFStream & in,movie_definition & m)146 DefineButtonTag::readDefineButton2Tag(SWFStream& in, movie_definition& m)
147 {
148 // Character ID has been read already
149
150 in.ensureBytes(1 + 2); // flags + actions offset
151
152 // Read the menu flag
153 // (this is a single bit, the first 7 bits are reserved)
154 const std::uint8_t flags = in.read_u8();
155 _trackAsMenu = flags & (1 << 0);
156 if (_trackAsMenu) {
157 LOG_ONCE(log_unimpl("DefineButton2: trackAsMenu"));
158 }
159
160 // Read the action offset
161 unsigned button_2_action_offset = in.read_u16();
162
163 unsigned long tagEndPosition = in.get_tag_end_position();
164 unsigned next_action_pos = in.tell() + button_2_action_offset - 2;
165
166 if ( next_action_pos > tagEndPosition )
167 {
168 IF_VERBOSE_MALFORMED_SWF(
169 log_swferror(_("Next Button2 actionOffset (%u) points past "
170 "the end of tag (%lu)"),
171 button_2_action_offset, tagEndPosition);
172 );
173 return;
174 }
175
176 unsigned long endOfButtonRecords = tagEndPosition;
177 if ( ! button_2_action_offset ) endOfButtonRecords = tagEndPosition;
178
179 // Read button records.
180 // takes at least 1 byte for the end mark button record, so
181 // we don't attempt to parse at all unless we have at least 1 byte left
182 while ( in.tell() < endOfButtonRecords )
183 {
184 ButtonRecord r;
185 if (r.read(in, SWF::DEFINEBUTTON2, m, endOfButtonRecords) == false) {
186 // Null record marks the end of button records.
187 break;
188 }
189
190 // SAFETY CHECK:
191 // if the ButtonRecord is corrupted, discard it
192 if (r.valid()) {
193 _buttonRecords.push_back(std::move(r));
194 }
195 }
196
197 if (button_2_action_offset) {
198
199 in.seek(next_action_pos);
200
201 // Read Button2ActionConditions
202 // Don't read past tag end
203 while (in.tell() < tagEndPosition) {
204 in.ensureBytes(2);
205 unsigned next_action_offset = in.read_u16();
206 if (next_action_offset) {
207 next_action_pos = in.tell() + next_action_offset - 2;
208 if (next_action_pos > tagEndPosition) {
209 IF_VERBOSE_MALFORMED_SWF(
210 log_swferror(_("Next action offset (%u) in "
211 "Button2ActionConditions points past "
212 "the end of tag"), next_action_offset);
213 );
214 next_action_pos = tagEndPosition;
215 }
216 }
217
218 const size_t endActionPos = next_action_offset ?
219 next_action_pos : tagEndPosition;
220
221 _buttonActions.push_back(new ButtonAction(in, SWF::DEFINEBUTTON2,
222 endActionPos, m));
223
224 if (next_action_offset == 0 ) {
225 // done.
226 break;
227 }
228
229 // seek to next action.
230 in.seek(next_action_pos);
231 }
232 }
233 }
234
235 DisplayObject*
createDisplayObject(Global_as & gl,DisplayObject * parent) const236 DefineButtonTag::createDisplayObject(Global_as& gl, DisplayObject* parent)
237 const
238 {
239 as_object* obj = getObjectWithPrototype(gl, NSV::CLASS_BUTTON);
240 DisplayObject* ch = new Button(obj, this, parent);
241 return ch;
242 }
243
244 int
getSWFVersion() const245 DefineButtonTag::getSWFVersion() const
246 {
247 return _movieDef.get_version();
248 }
249
250 bool
hasKeyPressHandler() const251 DefineButtonTag::hasKeyPressHandler() const
252 {
253 return std::find_if(_buttonActions.begin(), _buttonActions.end(),
254 std::mem_fn(&ButtonAction::triggeredByKeyPress)) !=
255 _buttonActions.end();
256 }
257
258 //
259 // ButtonAction
260 //
261
ButtonAction(SWFStream & in,TagType t,unsigned long endPos,movie_definition & mdef)262 ButtonAction::ButtonAction(SWFStream& in, TagType t, unsigned long endPos,
263 movie_definition& mdef)
264 :
265 _actions(mdef),
266 _conditions(OVER_DOWN_TO_OVER_UP)
267 {
268 // Read condition flags.
269 if (t != SWF::DEFINEBUTTON) {
270 assert(t == SWF::DEFINEBUTTON2);
271
272 if ( in.tell()+2 > endPos )
273 {
274 IF_VERBOSE_MALFORMED_SWF(
275 log_swferror(_("Premature end of button action input: "
276 "can't read conditions"));
277 );
278 return;
279 }
280 in.ensureBytes(2);
281 _conditions = in.read_u16();
282 }
283
284 IF_VERBOSE_PARSE (
285 log_parse(_(" button actions for conditions 0x%x"),
286 _conditions); // @@ need more info about which actions
287 );
288
289 // Read actions.
290 _actions.read(in, endPos);
291 }
292
293 bool
triggeredBy(const event_id & ev) const294 ButtonAction::triggeredBy(const event_id& ev) const
295 {
296 switch (ev.id()) {
297 case event_id::ROLL_OVER: return _conditions & IDLE_TO_OVER_UP;
298 case event_id::ROLL_OUT: return _conditions & OVER_UP_TO_IDLE;
299 case event_id::PRESS: return _conditions & OVER_UP_TO_OVER_DOWN;
300 case event_id::RELEASE: return _conditions & OVER_DOWN_TO_OVER_UP;
301 case event_id::DRAG_OUT: return _conditions & OVER_DOWN_TO_OUT_DOWN;
302 case event_id::DRAG_OVER: return _conditions & OUT_DOWN_TO_OVER_DOWN;
303 case event_id::RELEASE_OUTSIDE: return _conditions & OUT_DOWN_TO_IDLE;
304 case event_id::KEY_PRESS:
305 {
306 int keycode = getKeyCode();
307 if (!keycode) return false; // not a keypress event
308 return key::codeMap[ev.keyCode()][key::SWF] == keycode;
309 }
310 default: return false;
311 }
312 }
313
314 //
315 // ButtonRecord
316 //
317
318 DisplayObject*
instantiate(Button * button,bool name) const319 ButtonRecord::instantiate(Button* button, bool name) const
320 {
321 assert(button);
322 assert(_definitionTag);
323
324 Global_as& gl = getGlobal(*getObject(button));
325
326 DisplayObject* o = _definitionTag->createDisplayObject(gl, button);
327
328 o->setMatrix(_matrix, true);
329 o->setCxForm(_cxform);
330 o->set_depth(_buttonLayer + DisplayObject::staticDepthOffset + 1);
331 if (name && isReferenceable(*o)) {
332 o->set_name(button->getNextUnnamedInstanceName());
333 }
334 return o;
335 }
336
337 bool
hasState(Button::MouseState st) const338 ButtonRecord::hasState(Button::MouseState st) const
339 {
340 switch (st)
341 {
342 case Button::MOUSESTATE_UP: return _up;
343 case Button::MOUSESTATE_DOWN: return _down;
344 case Button::MOUSESTATE_OVER: return _over;
345 case Button::MOUSESTATE_HIT: return _hitTest;
346 default: return false;
347 }
348 }
349
350 bool
read(SWFStream & in,TagType t,movie_definition & m,unsigned long endPos)351 ButtonRecord::read(SWFStream& in, TagType t,
352 movie_definition& m, unsigned long endPos)
353 {
354 // caller should check this
355 if (in.tell() + 1 > endPos)
356 {
357 IF_VERBOSE_MALFORMED_SWF(
358 log_swferror(_(" premature end of button record input stream, "
359 "can't read flags"));
360 );
361 return false;
362 }
363
364 in.ensureBytes(1);
365 std::uint8_t flags = in.read_u8();
366 if (!flags) return false;
367
368 // Upper 4 bits are:
369 //
370 bool buttonHasBlendMode = flags & (1 << 5);
371 bool buttonHasFilterList = flags & (1 << 4);
372 _hitTest = flags & (1 << 3);
373 _down = flags & (1 << 2);
374 _over = flags & (1 << 1);
375 _up = flags & (1 << 0);
376
377 if (in.tell() + 2 > endPos) {
378 IF_VERBOSE_MALFORMED_SWF(
379 log_swferror(_(" premature end of button record input stream, "
380 "can't read DisplayObject id"));
381 );
382 return false;
383 }
384 in.ensureBytes(2);
385 const std::uint16_t id = in.read_u16();
386
387 // Get DisplayObject definition now (safer)
388 _definitionTag = m.getDefinitionTag(id);
389
390 // If no DisplayObject with given ID is found in the movie
391 // definition, we print an error, but keep parsing.
392 if (!_definitionTag) {
393 IF_VERBOSE_MALFORMED_SWF(
394 log_swferror(_(" button record for states [%s] refer to "
395 "DisplayObject with id %d, which is not found "
396 "in the chars dictionary"), computeButtonStatesString(flags), id);
397 );
398 }
399 else {
400 IF_VERBOSE_PARSE(
401 log_parse(_(" button record for states [%s] contain "
402 "DisplayObject %d (%s)"), computeButtonStatesString(flags),
403 id, typeName(*_definitionTag));
404 );
405 }
406
407 if (in.tell() + 2 > endPos) {
408 IF_VERBOSE_MALFORMED_SWF(
409 log_swferror(_(" premature end of button record input stream, "
410 "can't read button layer (depth?)"));
411 );
412 return false;
413 }
414 in.ensureBytes(2);
415 _buttonLayer = in.read_u16();
416
417 _matrix = readSWFMatrix(in);
418
419 if (t == SWF::DEFINEBUTTON2) {
420 _cxform = readCxFormRGBA(in);
421 }
422
423 if (buttonHasFilterList) {
424 filter_factory::read(in, true, &_filters);
425 LOG_ONCE(
426 log_unimpl("Button filters");
427 );
428 }
429
430 if (buttonHasBlendMode) {
431 in.ensureBytes(1);
432 _blendMode = in.read_u8();
433 LOG_ONCE(
434 log_unimpl("Button blend mode");
435 );
436 }
437
438 return true;
439 }
440
441 namespace {
442
443 std::string
computeButtonStatesString(int flags)444 computeButtonStatesString(int flags)
445 {
446 std::string ret;
447 if (flags & (1 << 3)) ret += "hit";
448 if (flags & (1 << 2)) { if (!ret.empty()) ret += ","; ret += "down"; }
449 if (flags & (1 << 1)) { if (!ret.empty()) ret += ","; ret += "over"; }
450 if (flags & (1 << 0)) { if (!ret.empty()) ret += ","; ret += "up"; }
451 return ret;
452 }
453
454 } // anonymous namespace
455
456 } // namespace SWF
457 } // namespace gnash
458
459 // Local Variables:
460 // mode: C++
461 // c-basic-offset: 8
462 // tab-width: 8
463 // indent-tabs-mode: t
464 // End:
465