Merge branch 'master' of git.sv.gnu.org:/srv/git/gnash
[gnash.git] / libcore / TextField.cpp
blob0676dfd644a7faab14185f3e92ac83faf7cc3edb
1 // TextField.cpp: User-editable text regions, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 // Things implemented:
21 // - setTextFormat does not discard target, url, tabStops, display or
22 // bullets
23 // - Above five fields are now implemented (except for target != blank)
24 // - Call movie_root getURL function to properly open url and target
26 // Things to work on:
27 // - For the url cases (url property and anchor tag in html) we should
28 // change the mouse cursor to the hand cursor standard for linkable
29 // text
31 #include "TextField.h"
33 #include <algorithm>
34 #include <string>
35 #include <cstdlib>
36 #include <cctype>
37 #include <utility>
38 #include <map>
39 #include <boost/assign/list_of.hpp>
40 #include <boost/bind.hpp>
41 #include <boost/tuple/tuple.hpp>
43 #include "utf8.h"
44 #include "log.h"
45 #include "swf/DefineEditTextTag.h"
46 #include "MovieClip.h"
47 #include "movie_root.h" // for killing focus
48 #include "as_environment.h"
49 #include "Font.h"
50 #include "fontlib.h"
51 #include "namedStrings.h"
52 #include "StringPredicates.h"
53 #include "TextFormat_as.h"
54 #include "GnashKey.h"
55 #include "TextRecord.h"
56 #include "Point2d.h"
57 #include "GnashNumeric.h"
58 #include "MouseButtonState.h"
59 #include "Global_as.h"
60 #include "Renderer.h"
61 #include "Transform.h"
62 #include "ObjectURI.h"
64 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
65 #define PADDING_TWIPS 40
67 // Define the following to get detailed log information about
68 // textfield bounds and HTML tags:
69 //#define GNASH_DEBUG_TEXTFIELDS 1
71 // Define this to get debugging info about text formatting
72 //#define GNASH_DEBUG_TEXT_FORMATTING 1
74 namespace gnash {
76 TextField::TextField(as_object* object, DisplayObject* parent,
77 const SWF::DefineEditTextTag& def)
79 InteractiveObject(object, parent),
80 _tag(&def),
81 _url(""),
82 _target(""),
83 _display(),
84 _tabStops(),
85 _variable_name(def.variableName()),
86 _backgroundColor(255,255,255,255),
87 _borderColor(0,0,0,255),
88 _textColor(def.color()),
89 _alignment(def.alignment()),
90 _font(0),
91 m_cursor(0u),
92 _glyphcount(0u),
93 _scroll(0u),
94 _maxScroll(1u),
95 _hScroll(0u),
96 _maxHScroll(0u),
97 _bottomScroll(0u),
98 _linesindisplay(0u),
99 _maxChars(def.maxChars()),
100 _autoSize(def.autoSize() ? AUTOSIZE_LEFT : AUTOSIZE_NONE),
101 _type(def.readOnly() ? typeDynamic : typeInput),
102 _bounds(def.bounds()),
103 _selection(0, 0),
104 _leading(def.leading()),
105 _indent(def.indent()),
106 _blockIndent(0),
107 _leftMargin(def.leftMargin()),
108 _rightMargin(def.rightMargin()),
109 _fontHeight(def.textHeight()),
110 _textDefined(def.hasText()),
111 _restrictDefined(false),
112 _underlined(false),
113 _bullet(false),
114 m_has_focus(false),
115 _multiline(def.multiline()),
116 _password(def.password()),
117 _text_variable_registered(false),
118 _drawBackground(def.border()),
119 _drawBorder(def.border()),
120 _embedFonts(def.getUseEmbeddedGlyphs()),
121 _wordWrap(def.wordWrap()),
122 _html(def.html()),
123 _selectable(!def.noSelect())
126 assert(object);
128 // WARNING! remember to set the font *before* setting text value!
129 boost::intrusive_ptr<const Font> f = def.getFont();
130 if (!f) f = fontlib::get_default_font();
131 setFont(f);
133 const int version = getSWFVersion(*object);
135 // set default text *before* calling registerTextVariable
136 // (if the textvariable already exist and has a value
137 // the text will be replaced with it)
138 if (_textDefined) {
139 setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
142 init();
146 TextField::TextField(as_object* object, DisplayObject* parent,
147 const SWFRect& bounds)
149 InteractiveObject(object, parent),
150 _url(""),
151 _target(""),
152 _display(),
153 _tabStops(),
154 _backgroundColor(255,255,255,255),
155 _borderColor(0, 0, 0, 255),
156 _textColor(0, 0, 0, 255),
157 _alignment(ALIGN_LEFT),
158 _font(0),
159 m_cursor(0u),
160 _glyphcount(0u),
161 _scroll(0u),
162 _maxScroll(1u),
163 _hScroll(0u),
164 _maxHScroll(0u),
165 _bottomScroll(0u),
166 _linesindisplay(0u),
167 _maxChars(0),
168 _autoSize(AUTOSIZE_NONE),
169 _type(typeDynamic),
170 _bounds(bounds),
171 _selection(0, 0),
172 _leading(0),
173 _indent(0),
174 _blockIndent(0),
175 _leftMargin(0),
176 _rightMargin(0),
177 _fontHeight(12 * 20),
178 _textDefined(false),
179 _restrictDefined(false),
180 _underlined(false),
181 _bullet(false),
182 m_has_focus(false),
183 _multiline(false),
184 _password(false),
185 _text_variable_registered(false),
186 _drawBackground(false),
187 _drawBorder(false),
188 _embedFonts(false),
189 _wordWrap(false),
190 _html(false),
191 _selectable(true)
193 // Use the default font (Times New Roman for Windows, Times for Mac
194 // according to docs. They don't say what it is for Linux.
195 boost::intrusive_ptr<const Font> f = fontlib::get_default_font();
196 setFont(f);
198 init();
201 void
202 TextField::init()
204 registerTextVariable();
206 reset_bounding_box(0, 0);
210 TextField::~TextField()
214 void
215 TextField::removeTextField()
217 int depth = get_depth();
218 if ( depth < 0 || depth > 1048575 )
220 //IF_VERBOSE_ASCODING_ERRORS(
221 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
222 "out of the 'dynamic' zone [0..1048575], won't remove"),
223 getTarget(), depth);
224 //);
225 return;
228 DisplayObject* p = parent();
229 assert(p); // every TextField must have a parent, right ?
231 MovieClip* parentSprite = p->to_movie();
233 if (!parentSprite) {
234 log_error("FIXME: attempt to remove a TextField being a child of a %s",
235 typeName(*p));
236 return;
239 // second argument is arbitrary, see comments above
240 // the function declaration in MovieClip.h
241 parentSprite->remove_display_object(depth, 0);
244 void
245 TextField::show_cursor(Renderer& renderer, const SWFMatrix& mat)
247 if (_textRecords.empty()) {
248 return;
250 boost::uint16_t x;
251 boost::uint16_t y;
252 boost::uint16_t h;
253 size_t i = cursorRecord();
254 SWF::TextRecord record = _textRecords[i];
256 x = record.xOffset();
257 y = record.yOffset() - record.textHeight() + getLeading();
258 h = record.textHeight();
260 if (!record.glyphs().empty()) {
261 for (unsigned int p = 0 ; p < (m_cursor - _recordStarts[i]); ++p) {
262 x += record.glyphs()[p].advance;
266 const std::vector<point> box = boost::assign::list_of
267 (point(x, y))
268 (point(x, y + h));
270 renderer.drawLine(box, rgba(0, 0, 0, 255), mat);
273 size_t
274 TextField::cursorRecord()
276 SWF::TextRecord record;
277 size_t i = 0;
279 if (_textRecords.size() != 0) {
280 while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
281 ++i;
283 return i-1;
285 return 0;
288 void
289 TextField::display(Renderer& renderer, const Transform& base)
291 const DisplayObject::MaskRenderer mr(renderer, *this);
293 registerTextVariable();
295 const bool drawBorder = getDrawBorder();
296 const bool drawBackground = getDrawBackground();
298 Transform xform = base * transform();
300 // This is a hack to handle device fonts, which are not affected by
301 // color transform.
302 if (!getEmbedFonts()) xform.colorTransform = SWFCxForm();
304 if ((drawBorder || drawBackground) && !_bounds.is_null()) {
306 std::vector<point> coords(4);
308 boost::int32_t xmin = _bounds.get_x_min();
309 boost::int32_t xmax = _bounds.get_x_max();
310 boost::int32_t ymin = _bounds.get_y_min();
311 boost::int32_t ymax = _bounds.get_y_max();
313 coords[0].setTo(xmin, ymin);
314 coords[1].setTo(xmax, ymin);
315 coords[2].setTo(xmax, ymax);
316 coords[3].setTo(xmin, ymax);
318 rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
319 rgba backgroundColor = drawBackground ? getBackgroundColor() :
320 rgba(0,0,0,0);
322 SWFCxForm cx = xform.colorTransform;
324 if (drawBorder) borderColor = cx.transform(borderColor);
326 if (drawBackground) backgroundColor = cx.transform(backgroundColor);
328 #ifdef GNASH_DEBUG_TEXTFIELDS
329 log_debug("rendering a Pol composed by corners %s", _bounds);
330 #endif
332 renderer.draw_poly(&coords.front(), 4, backgroundColor,
333 borderColor, xform.matrix, true);
337 // Draw our actual text.
338 // Using a SWFMatrix to translate to def bounds seems an hack to me.
339 // A cleaner implementation is likely correctly setting the
340 // _xOffset and _yOffset memebers in glyph records.
341 // Anyway, see bug #17954 for a testcase.
342 if (!_bounds.is_null()) {
343 xform.matrix.concatenate_translation(_bounds.get_x_min(),
344 _bounds.get_y_min());
347 _displayRecords.clear();
348 float scale = getFontHeight() /
349 static_cast<float>(_font->unitsPerEM(_embedFonts));
350 float fontLeading = _font->leading() * scale;
352 //offset the lines
353 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
354 size_t recordline;
355 for (size_t i = 0; i < _textRecords.size(); ++i) {
356 recordline = 0;
357 //find the line the record is on
358 while (recordline < _line_starts.size() &&
359 _line_starts[recordline] <= _recordStarts[i]) {
360 ++recordline;
362 //offset the line
363 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
364 //add the lines we want to the display record
365 if (_textRecords[i].yOffset() > 0 &&
366 _textRecords[i].yOffset() < _bounds.height()) {
367 _displayRecords.push_back(_textRecords[i]);
371 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
372 _embedFonts);
374 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
376 clear_invalidated();
380 void
381 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
383 if (!force && !invalidated()) return; // no need to redraw
385 ranges.add(m_old_invalidated_ranges);
387 const SWFMatrix& wm = getWorldMatrix(*this);
389 SWFRect bounds = getBounds();
390 bounds.expand_to_rect(m_text_bounding_box);
391 wm.transform(bounds);
392 ranges.add(bounds.getRange());
395 void
396 TextField::setRestrict(const std::string& restrict)
398 _restrictDefined = true;
400 std::string::const_iterator rit = restrict.begin();
401 std::string::const_iterator re = restrict.end();
402 std::set<wchar_t>::const_iterator locate;
404 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
405 for (unsigned int i = 0; i <= 255; ++i) {
406 _restrictedchars.insert(char(i));
408 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
409 _restrictedchars.clear();
412 while (rit != re) {
413 while (rit != re && *rit != '^') { //This loop allows chars
414 if (*rit == '-') {
415 log_error("invalid restrict string");
416 return;
417 } else if (*(rit+1) == '-') {
418 if (re - (rit+2) != 0) {
419 unsigned int q = *(rit+2);
420 for (unsigned int p = *rit; p <= q; (++p)){
421 _restrictedchars.insert(char(p));
423 rit += 3;
424 } else {
425 log_error("invalid restrict string");
426 return;
428 } else if (*rit == '\\') {
429 ++rit;
430 _restrictedchars.insert(*rit);
431 ++rit;
432 } else {
433 _restrictedchars.insert(*rit);
434 ++rit;
437 if (rit != re) {
438 ++rit;
440 while (rit != re && *rit != '^') { //This loop restricts chars
441 locate = _restrictedchars.find(*rit);
442 if (*rit == '-') {
443 log_error("invalid restrict string");
444 return;
445 } else if (*(rit+1) == '-') {
446 if (re - (rit+2) != 0) {
447 unsigned int q = *(rit+2);
448 for (unsigned int p = *rit; p <= q; ++p){
449 locate = _restrictedchars.find(p);
450 if(locate != _restrictedchars.end()) {
451 _restrictedchars.erase(locate);
454 ++rit;
455 ++rit;
456 ++rit;
457 } else {
458 log_error("invalid restrict string");
459 return;
461 } else if (*rit == '\\') {
462 ++rit;
463 locate = _restrictedchars.find(*rit);
464 if(locate != _restrictedchars.end()) {
465 _restrictedchars.erase(locate);
467 ++rit;
468 } else {
469 if(locate != _restrictedchars.end()) {
470 _restrictedchars.erase(locate);
472 ++rit;
475 if (rit != re) {
476 ++rit;
479 _restrict = restrict;
482 void
483 TextField::replaceSelection(const std::string& replace)
485 const int version = getSWFVersion(*getObject(this));
486 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
488 assert(_selection.second >= _selection.first);
489 assert(_selection.second <= _text.size());
490 assert(_selection.first <= _text.size());
492 // If the text has changed but the selection hasn't, make sure we
493 // don't access it out of bounds.
494 const size_t start = _selection.first;
495 const size_t end = _selection.second;
497 const size_t replaceLength = wstr.size();
499 _text.replace(start, end - start, wstr);
500 _selection = std::make_pair(start + replaceLength, start + replaceLength);
503 void
504 TextField::setSelection(int start, int end)
506 if (_text.empty()) {
507 _selection = std::make_pair(0, 0);
508 return;
511 const size_t textLength = _text.size();
513 if (start < 0) start = 0;
514 else start = std::min<size_t>(start, textLength);
516 if (end < 0) end = 0;
517 else end = std::min<size_t>(end, textLength);
519 // The cursor position is always set to the end value, even if the
520 // two values are swapped to obtain the selection. Equal values are
521 // fine.
522 m_cursor = end;
523 if (start > end) std::swap(start, end);
525 _selection = std::make_pair(start, end);
528 void
529 TextField::notifyEvent(const event_id& ev)
531 switch (ev.id())
533 case event_id::PRESS:
535 movie_root& root = stage();
536 boost::int32_t x_mouse, y_mouse;
537 boost::tie(x_mouse, y_mouse) = root.mousePosition();
539 SWFMatrix m = getMatrix(*this);
541 x_mouse -= m.get_x_translation();
542 y_mouse -= m.get_y_translation();
544 SWF::TextRecord rec;
546 for (size_t i=0; i < _textRecords.size(); ++i) {
547 if ((x_mouse > _textRecords[i].xOffset()) &&
548 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
549 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
550 (y_mouse < _textRecords[i].yOffset())) {
551 rec = _textRecords[i];
552 break;
556 if (!rec.getURL().empty()) {
557 root.getURL(rec.getURL(), rec.getTarget(), "",
558 MovieClip::METHOD_NONE);
561 break;
563 case event_id::KEY_PRESS:
565 setHtml(false); //editable html fields are not yet implemented
566 std::wstring s = _text;
568 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
569 // The maximum value is about 265, including function keys.
570 // It seems that typing in DisplayObjects outside the Latin-1 set
571 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
572 // is not supported, though a much greater number UTF-8 codes can be
573 // stored and displayed. See utf.h for more information.
574 // This is a limit on the number of key codes, not on the
575 // capacity of strings.
576 gnash::key::code c = ev.keyCode();
579 // maybe _text is changed in ActionScript
580 m_cursor = std::min<size_t>(m_cursor, _text.size());
582 size_t cur_cursor = m_cursor;
583 size_t previouslinesize = 0;
584 size_t nextlinesize = 0;
585 size_t manylines = _line_starts.size();
586 LineStarts::iterator linestartit = _line_starts.begin();
587 LineStarts::const_iterator linestartend = _line_starts.end();
589 switch (c)
591 case key::BACKSPACE:
592 if (isReadOnly()) return;
593 if (m_cursor > 0)
595 s.erase(m_cursor - 1, 1);
596 m_cursor--;
597 setTextValue(s);
599 break;
601 case key::DELETEKEY:
602 if (isReadOnly()) return;
603 if (_glyphcount > m_cursor)
605 s.erase(m_cursor, 1);
606 setTextValue(s);
608 break;
610 case key::INSERT: // TODO
611 if (isReadOnly()) return;
612 break;
614 case key::HOME:
615 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
616 cur_cursor = *linestartit;
617 linestartit++;
619 m_cursor = cur_cursor;
620 break;
622 case key::PGUP:
623 // if going a page up is too far...
624 if(_scroll < _linesindisplay) {
625 _scroll = 0;
626 m_cursor = 0;
627 } else { // go a page up
628 _scroll -= _linesindisplay;
629 m_cursor = _line_starts[_scroll];
631 scrollLines();
632 break;
634 case key::UP:
635 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
636 cur_cursor = *linestartit;
637 linestartit++;
639 //if there is no previous line
640 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
641 m_cursor = 0;
642 break;
644 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
645 //if the previous line is smaller
646 if (m_cursor - cur_cursor > previouslinesize) {
647 m_cursor = *(--(--linestartit)) + previouslinesize;
648 } else {
649 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
651 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
652 --_scroll;
654 scrollLines();
655 break;
657 case key::END:
658 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
659 linestartit++;
661 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
662 break;
664 case key::PGDN:
665 //if going another page down is too far...
666 if(_scroll + _linesindisplay >= manylines) {
667 if(manylines - _linesindisplay <= 0) {
668 _scroll = 0;
669 } else {
670 _scroll = manylines - _linesindisplay;
672 if(m_cursor < _line_starts[_scroll-1]) {
673 m_cursor = _line_starts[_scroll-1];
674 } else {
675 m_cursor = _text.size();
677 } else { //go a page down
678 _scroll += _linesindisplay;
679 m_cursor = _line_starts[_scroll];
681 scrollLines();
682 break;
684 case key::DOWN:
686 while (linestartit < linestartend &&
687 *linestartit <= m_cursor ) {
688 cur_cursor = *linestartit;
689 linestartit++;
692 // linestartit should never be before _line_starts.begin()
693 const size_t currentLine = linestartit -
694 _line_starts.begin();
696 //if there is no next line
697 if (currentLine >= manylines ) {
698 m_cursor = _text.size();
699 break;
701 nextlinesize = _textRecords[currentLine].glyphs().size();
703 //if the next line is smaller
704 if (m_cursor - cur_cursor > nextlinesize) {
705 m_cursor = *linestartit + nextlinesize;
706 } else {
707 //put the cursor at the same character distance
708 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
710 if (_line_starts.size() > _linesindisplay &&
711 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
712 ++_scroll;
714 scrollLines();
715 break;
718 case key::LEFT:
719 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
720 break;
722 case key::RIGHT:
723 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
724 _glyphcount;
725 break;
727 case key::ENTER:
728 if (isReadOnly()) return;
729 if (!multiline()) break;
731 default:
733 if (maxChars() != 0) {
734 if (_maxChars <= _glyphcount) {
735 break;
739 if (isReadOnly()) return;
740 wchar_t t = static_cast<wchar_t>(
741 gnash::key::codeMap[c][key::ASCII]);
742 if (t != 0) {
744 if (!_restrictDefined) {
745 // Insert one copy of the character
746 // at the cursor position.
747 s.insert(m_cursor, 1, t);
748 m_cursor++;
749 } else if (_restrictedchars.count(t)) {
750 // Insert one copy of the character
751 // at the cursor position.
752 s.insert(m_cursor, 1, t);
753 m_cursor++;
754 } else if (_restrictedchars.count(tolower(t))) {
755 // restrict substitutes the opposite case
756 s.insert(m_cursor, 1, tolower(t));
757 m_cursor++;
758 } else if (_restrictedchars.count(toupper(t))) {
759 // restrict substitutes the opposite case
760 s.insert(m_cursor, 1, toupper(t));
761 m_cursor++;
764 setTextValue(s);
766 onChanged();
767 set_invalidated();
770 default:
771 return;
775 InteractiveObject*
776 TextField::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
778 if (!visible()) return 0;
780 // Not selectable, so don't catch mouse events!
781 if (!_selectable) return 0;
783 SWFMatrix m = getMatrix(*this);
784 point p(x, y);
785 m.invert().transform(p);
787 if (_bounds.point_test(p.x, p.y)) return this;
789 return 0;
792 void
793 TextField::updateText(const std::string& str)
795 const int version = getSWFVersion(*getObject(this));
796 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
797 updateText(wstr);
800 void
801 TextField::updateText(const std::wstring& wstr)
803 _textDefined = true;
804 if (_text == wstr) return;
806 set_invalidated();
808 _text = wstr;
810 _selection.first = std::min(_selection.first, _text.size());
811 _selection.second = std::min(_selection.second, _text.size());
813 format_text();
816 void
817 TextField::updateHtmlText(const std::wstring& wstr)
819 if (_htmlText == wstr) return;
821 set_invalidated();
823 _htmlText = wstr;
824 format_text();
827 void
828 TextField::setTextValue(const std::wstring& wstr)
830 updateHtmlText(wstr);
831 updateText(wstr);
833 if (!_variable_name.empty() && _text_variable_registered) {
834 // TODO: notify MovieClip if we have a variable name !
835 VariableRef ref = parseTextVariableRef(_variable_name);
836 as_object* tgt = ref.first;
837 if (tgt) {
838 const int version = getSWFVersion(*getObject(this));
839 // we shouldn't truncate, right?
840 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
841 version));
843 else {
844 // nothing to do (too early ?)
845 log_debug("setTextValue: variable name %s points to a non-existent"
846 " target, I guess we would not be registered if this was "
847 "true, or the sprite we've registered our variable name "
848 "has been unloaded", _variable_name);
853 std::string
854 TextField::get_text_value() const
856 // we need the const_cast here because registerTextVariable
857 // *might* change our text value, calling the non-const
858 // setTextValue().
859 // This happens if the TextVariable has not been already registered
860 // and during registration comes out to name an existing variable
861 // with a pre-existing value.
862 const_cast<TextField*>(this)->registerTextVariable();
864 const int version = getSWFVersion(*getObject(this));
866 return utf8::encodeCanonicalString(_text, version);
869 std::string
870 TextField::get_htmltext_value() const
872 const_cast<TextField*>(this)->registerTextVariable();
873 const int version = getSWFVersion(*getObject(this));
874 return utf8::encodeCanonicalString(_htmlText, version);
877 void
878 TextField::setTextFormat(TextFormat_as& tf)
880 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
881 //This is just so we can set individual variables without having to call format_text()
882 //This calls format_text() at the end of setting TextFormat
883 if (tf.align()) setAlignment(*tf.align());
884 if (tf.size()) setFontHeight(*tf.size()); // keep twips
885 if (tf.indent()) setIndent(*tf.indent());
886 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
887 if (tf.leading()) setLeading(*tf.leading());
888 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
889 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
890 if (tf.color()) setTextColor(*tf.color());
891 if (tf.underlined()) setUnderlined(*tf.underlined());
892 if (tf.bullet()) setBullet(*tf.bullet());
893 setDisplay(tf.display());
894 if (tf.tabStops()) setTabStops(*tf.tabStops());
896 // NEED TO IMPLEMENT THESE TWO
897 if (tf.url()) setURL(*tf.url());
898 if (tf.target()) setTarget(*tf.target());
900 format_text();
903 float
904 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
906 float width = _bounds.width();
907 float right_margin = getRightMargin();
909 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
911 if (extra_space <= 0.0f) {
912 #ifdef GNASH_DEBUG_TEXTFIELDS
913 log_debug(_("TextField text doesn't fit in its boundaries: "
914 "width %g, margin %g - nothing to align"),
915 width, right_margin);
916 #endif
917 return 0.0f;
920 float shift_right = 0.0f;
922 switch (align) {
923 case ALIGN_LEFT:
924 // Nothing to do; already aligned left.
925 return 0.0f;
926 case ALIGN_CENTER:
927 // Distribute the space evenly on both sides.
928 shift_right = extra_space / 2;
929 break;
930 case ALIGN_RIGHT:
931 // Shift all the way to the right.
932 shift_right = extra_space;
933 break;
934 case ALIGN_JUSTIFY:
935 // What should we do here?
936 break;
939 // Shift the beginnings of the records on this line.
940 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
941 SWF::TextRecord& rec = _textRecords[i];
942 rec.setXOffset(rec.xOffset() + shift_right);
944 return shift_right;
947 boost::intrusive_ptr<const Font>
948 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
950 if (newfont == _font) return _font;
952 boost::intrusive_ptr<const Font> oldfont = _font;
953 set_invalidated();
954 _font = newfont;
955 format_text();
956 return oldfont;
960 void
961 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
963 // tab (ASCII HT)
964 const int space = 32;
965 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
966 if (index == -1) {
967 IF_VERBOSE_MALFORMED_SWF (
968 log_error(_("TextField: missing glyph for space char (needed "
969 "for TAB). Make sure DisplayObject shapes for font "
970 "%s are being exported into your SWF file."),
971 rec.getFont()->name());
974 else {
975 // TODO: why is there a copy of the vector?
976 std::vector<int> tabStops = _tabStops;
978 std::sort(_tabStops.begin(), _tabStops.end());
980 int tab = 0;
981 if (!_tabStops.empty()) {
982 tab = _tabStops.back() + 1;
984 for (size_t i = 0; i < tabStops.size(); ++i) {
985 if (tabStops[i] > x) {
986 if((tabStops[i] - x) < tab) {
987 tab = tabStops[i] - x;
993 // This is necessary in case the number of tabs in the text
994 // are more than the actual number of tabStops inside the
995 // vector
996 if (tab != _tabStops.back() + 1) {
997 SWF::TextRecord::GlyphEntry ge;
998 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
999 ge.advance = tab;
1000 rec.addGlyph(ge);
1001 x+=ge.advance;
1004 else {
1005 SWF::TextRecord::GlyphEntry ge;
1006 ge.index = index;
1007 ge.advance = scale * rec.getFont()->get_advance(index,
1008 _embedFonts);
1010 const int tabstop = 4;
1011 rec.addGlyph(ge, tabstop);
1012 x += ge.advance * tabstop;
1017 void
1018 TextField::format_text()
1020 _textRecords.clear();
1021 _line_starts.clear();
1022 _recordStarts.clear();
1023 _glyphcount = 0;
1025 _recordStarts.push_back(0);
1027 // nothing more to do if text is empty
1028 if (_text.empty()) {
1029 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1030 // not sure we should...
1031 reset_bounding_box(0, 0);
1032 return;
1035 LineStarts::iterator linestartit = _line_starts.begin();
1036 LineStarts::const_iterator linestartend = _line_starts.end();
1038 AutoSize autoSize = getAutoSize();
1039 if (autoSize != AUTOSIZE_NONE) {
1040 // When doing WordWrap we don't want to change
1041 // the boundaries. See bug #24348
1042 if (!doWordWrap()) {
1043 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1047 // FIXME: I don't think we should query the definition
1048 // to find the appropriate font to use, as ActionScript
1049 // code should be able to change the font of a TextField
1050 if (!_font) {
1051 log_error(_("No font for TextField!"));
1052 return;
1055 boost::uint16_t fontHeight = getFontHeight();
1056 float scale = fontHeight /
1057 static_cast<float>(_font->unitsPerEM(_embedFonts));
1058 const float fontLeading = _font->leading() * scale;
1059 const boost::uint16_t leftMargin = getLeftMargin();
1060 const boost::uint16_t indent = getIndent();
1061 const boost::uint16_t blockIndent = getBlockIndent();
1062 const bool underlined = getUnderlined();
1064 /// Remember the current bounds for autosize.
1065 SWFRect oldBounds(_bounds);
1067 SWF::TextRecord rec; // one to work on
1068 rec.setFont(_font.get());
1069 rec.setUnderline(underlined);
1070 rec.setColor(getTextColor());
1071 rec.setXOffset(PADDING_TWIPS +
1072 std::max(0, leftMargin + indent + blockIndent));
1073 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1074 rec.setTextHeight(fontHeight);
1076 // create in textrecord.h
1077 rec.setURL(_url);
1078 rec.setTarget(_target);
1080 // BULLET CASE:
1082 // First, we indent 10 spaces, and then place the bullet
1083 // character (in this case, an asterisk), then we pad it
1084 // again with 10 spaces
1085 // Note: this works only for additional lines of a
1086 // bulleted list, so that is why there is a bullet format
1087 // in the beginning of format_text()
1088 if (_bullet) {
1089 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1091 SWF::TextRecord::GlyphEntry ge;
1092 ge.index = space;
1093 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1094 rec.addGlyph(ge, 5);
1096 // We use an asterisk instead of a bullet
1097 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1098 ge.index = bullet;
1099 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1100 rec.addGlyph(ge);
1102 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1103 ge.index = space;
1104 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1105 rec.addGlyph(ge, 4);
1108 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1109 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1111 // Start the bbox at the upper-left corner of the first glyph.
1112 //reset_bounding_box(x, y + fontHeight);
1114 int last_code = -1; // only used if _embedFonts
1115 int last_space_glyph = -1;
1116 size_t last_line_start_record = 0;
1118 _line_starts.push_back(0);
1120 // String iterators are very sensitive to
1121 // potential changes to the string (to allow for copy-on-write).
1122 // So there must be no external changes to the string or
1123 // calls to most non-const member functions during this loop.
1124 // Especially not c_str() or data().
1125 std::wstring::const_iterator it = _text.begin();
1126 const std::wstring::const_iterator e = _text.end();
1128 ///handleChar takes care of placing the glyphs
1129 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1130 last_line_start_record);
1132 // Expand bounding box to include the whole text (if autoSize and wordWrap
1133 // is not in operation.
1134 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1136 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1138 if (_autoSize == AUTOSIZE_RIGHT) {
1139 /// Autosize right expands from the previous right margin.
1140 SWFMatrix m;
1142 m.set_x_translation(oldBounds.get_x_max() - _bounds.width());
1143 m.transform(_bounds);
1145 else if (_autoSize == AUTOSIZE_CENTER) {
1146 // Autosize center expands from the previous center.
1147 SWFMatrix m;
1148 m.set_x_translation(oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1149 _bounds.width() / 2.0);
1150 m.transform(_bounds);
1154 // Add the last line to our output.
1155 _textRecords.push_back(rec);
1157 // align the last (or single) line
1158 align_line(getTextAlignment(), last_line_start_record, x);
1160 scrollLines();
1162 set_invalidated(); //redraw
1166 void
1167 TextField::scrollLines()
1169 boost::uint16_t fontHeight = getFontHeight();
1170 float scale = fontHeight /
1171 static_cast<float>(_font->unitsPerEM(_embedFonts));
1172 float fontLeading = _font->leading() * scale;
1173 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1174 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1175 size_t manylines = _line_starts.size();
1176 size_t lastvisibleline = _scroll + _linesindisplay;
1177 size_t line = 0;
1179 // If there aren't as many lines as we have scrolled, display the
1180 // end of the text.
1181 if (manylines < _scroll) {
1182 _scroll = manylines - _linesindisplay;
1183 return;
1186 // which line is the cursor on?
1187 while (line < manylines && _line_starts[line] <= m_cursor) {
1188 ++line;
1191 if (manylines - _scroll <= _linesindisplay) {
1192 // This is for if we delete a line
1193 if (manylines < _linesindisplay) _scroll = 0;
1194 else {
1195 _scroll = manylines - _linesindisplay;
1197 } else if (line < _scroll) {
1198 //if we are at a higher position, scroll the lines down
1199 _scroll -= _scroll - line;
1200 } else if (manylines > _scroll + _linesindisplay) {
1201 //if we are at a lower position, scroll the lines up
1202 if (line >= (_scroll+_linesindisplay)) {
1203 _scroll += line - (lastvisibleline);
1209 void
1210 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1211 SWF::TextRecord& rec, int& last_space_glyph,
1212 LineStarts::value_type& last_line_start_record, float div)
1214 // newline.
1215 LineStarts::iterator linestartit = _line_starts.begin();
1216 LineStarts::const_iterator linestartend = _line_starts.end();
1218 float scale = _fontHeight /
1219 static_cast<float>(_font->unitsPerEM(_embedFonts));
1220 float fontLeading = _font->leading() * scale;
1221 float leading = getLeading();
1222 leading += fontLeading * scale; // not sure this is correct...
1224 // Close out this stretch of glyphs.
1225 ++_glyphcount;
1226 _textRecords.push_back(rec);
1227 _recordStarts.push_back(_glyphcount);
1228 align_line(getTextAlignment(), last_line_start_record, x);
1230 // Expand bounding box to include last column of text ...
1231 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1232 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1235 // new paragraphs get the indent.
1236 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1237 PADDING_TWIPS;
1238 y += div * (getFontHeight() + leading);
1239 if (y >= _bounds.height()) {
1240 ++_maxScroll;
1243 // Start a new record on the next line. Other properties of the
1244 // TextRecord should be left unchanged.
1245 rec.clearGlyphs();
1246 rec.setXOffset(x);
1247 rec.setYOffset(y);
1249 last_space_glyph = -1;
1250 last_line_start_record = _textRecords.size();
1252 linestartit = _line_starts.begin();
1253 linestartend = _line_starts.end();
1254 //Fit a line_start in the correct place
1255 const size_t currentPos = _glyphcount;
1257 while (linestartit < linestartend && *linestartit < currentPos)
1259 ++linestartit;
1261 _line_starts.insert(linestartit, currentPos);
1263 // BULLET CASE:
1265 // First, we indent 10 spaces, and then place the bullet
1266 // character (in this case, an asterisk), then we pad it
1267 // again with 10 spaces
1268 // Note: this works only for additional lines of a
1269 // bulleted list, so that is why there is a bullet format
1270 // in the beginning of format_text()
1271 if (_bullet)
1273 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1274 SWF::TextRecord::GlyphEntry ge;
1275 ge.index = space;
1276 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1278 rec.addGlyph(ge,5);
1279 _glyphcount += 5;
1281 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1282 ge.index = bullet;
1283 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1284 rec.addGlyph(ge);
1285 ++_glyphcount;
1287 ge.index = space;
1288 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1290 rec.addGlyph(ge,4);
1291 _glyphcount += 4;
1295 void
1296 TextField::handleChar(std::wstring::const_iterator& it,
1297 const std::wstring::const_iterator& e, boost::int32_t& x,
1298 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1299 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1301 LineStarts::iterator linestartit = _line_starts.begin();
1302 LineStarts::const_iterator linestartend = _line_starts.end();
1304 float scale = _fontHeight /
1305 static_cast<float>(_font->unitsPerEM(_embedFonts));
1306 float fontDescent = _font->descent(_embedFonts) * scale;
1307 float fontLeading = _font->leading() * scale;
1308 float leading = getLeading();
1309 leading += fontLeading * scale; // not sure this is correct...
1311 boost::uint32_t code = 0;
1312 while (it != e)
1314 code = *it++;
1315 if (!code) break;
1317 if ( _embedFonts )
1319 x += rec.getFont()->get_kerning_adjustment(last_code,
1320 static_cast<int>(code)) * scale;
1321 last_code = static_cast<int>(code);
1324 // Expand the bounding-box to the lower-right corner of each glyph as
1325 // we generate it.
1326 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1327 switch (code)
1329 case 27:
1330 // Ignore escape
1331 break;
1332 case 9:
1333 insertTab(rec, x, scale);
1334 break;
1335 case 8:
1336 // Backspace
1338 // This is a limited hack to enable overstrike effects.
1339 // It backs the cursor up by one DisplayObject and then continues
1340 // the layout. E.g. you can use this to display an underline
1341 // cursor inside a simulated text-entry box.
1343 // ActionScript understands the '\b' escape sequence
1344 // for inserting a BS DisplayObject.
1346 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1347 // OVER NEWLINES, ETC.
1349 if (!rec.glyphs().empty())
1351 // Peek at the previous glyph, and zero out its advance
1352 // value, so the next char overwrites it.
1353 float advance = rec.glyphs().back().advance;
1354 x -= advance;
1355 // Remove one glyph
1356 rec.clearGlyphs(1);
1358 continue;
1359 case 13:
1360 case 10:
1362 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1363 break;
1365 case '<':
1366 if (doHtml())
1368 //close out this stretch of glyphs
1369 _textRecords.push_back(rec);
1370 rec.clearGlyphs();
1371 _recordStarts.push_back(_glyphcount);
1372 if (*it == '/') {
1373 while (it != e && *it != '>') {
1374 ++it;
1376 ++it;
1377 return;
1379 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1380 "gnash will just ignore the tags and "
1381 "print their content")));
1383 std::wstring discard;
1384 std::map<std::string,std::string> attributes;
1385 SWF::TextRecord newrec;
1386 newrec.setFont(rec.getFont());
1387 newrec.setUnderline(rec.underline());
1388 newrec.setColor(rec.color());
1389 newrec.setTextHeight(rec.textHeight());
1390 newrec.setXOffset(x);
1391 newrec.setYOffset(y);
1392 bool selfclosing = false;
1393 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1394 std::string s(discard.begin(), discard.end());
1396 std::map<std::string,std::string>::const_iterator attloc;
1398 if (!complete) {
1399 //parsing went wrong
1400 continue;
1401 } else {
1402 // Don't think this is the best way to match with
1403 // tags...
1404 // TODO: assumes tags are properly nested. This isn't
1405 // correct.
1406 if (s == "U") {
1407 //underline
1408 newrec.setUnderline(true);
1409 handleChar(it, e, x, y, newrec, last_code,
1410 last_space_glyph, last_line_start_record);
1412 else if (s == "A") {
1413 // anchor (blue text).
1414 rgba color(0, 0, 0xff, 0xff);
1415 newrec.setColor(color);
1416 newrec.setUnderline(true);
1417 attloc = attributes.find("HREF");
1418 if (attloc != attributes.end()) {
1419 newrec.setURL(attloc->second);
1421 attloc = attributes.find("TARGET");
1422 if (attloc !=attributes.end()) {
1423 newrec.setTarget(attloc->second);
1425 handleChar(it, e, x, y, newrec, last_code,
1426 last_space_glyph, last_line_start_record);
1428 else if (s == "B") {
1429 //bold
1430 Font* boldfont = new Font(rec.getFont()->name(),
1431 true, rec.getFont()->isItalic());
1432 newrec.setFont(boldfont);
1433 handleChar(it, e, x, y, newrec, last_code,
1434 last_space_glyph, last_line_start_record);
1436 else if (s == "FONT") {
1437 //font
1438 boost::uint16_t originalsize = _fontHeight;
1439 attloc = attributes.find("COLOR");
1440 if (attloc != attributes.end()) {
1441 std::string hexval(attloc->second);
1442 if (hexval.empty() || hexval[0] != '#') {
1443 // FIXME: should this be a log_aserror
1444 // or log_unimpl ? It is triggered
1445 // by TextFieldHTML.as
1446 log_error("Unexpected value '%s' in "
1447 "TextField font color attribute",
1448 hexval);
1450 else {
1451 hexval.erase(0, 1);
1452 // font COLOR attribute
1453 const rgba color =
1454 colorFromHexString(hexval);
1455 newrec.setColor(color);
1458 attloc = attributes.find("FACE");
1459 if (attloc != attributes.end()) {
1460 //font FACE attribute
1461 Font* newfont = new Font(attloc->second,
1462 rec.getFont()->isBold(), rec.getFont()->isItalic());
1463 newrec.setFont(newfont);
1465 attloc = attributes.find("SIZE");
1466 if (attloc != attributes.end()) {
1467 //font SIZE attribute
1468 std::string firstchar = attloc->second.substr(0,1);
1469 if (firstchar == "+") {
1470 newrec.setTextHeight(rec.textHeight() +
1472 (pixelsToTwips(std::strtol(
1473 attloc->second.substr(1,attloc->second.length()-1).data(),
1474 NULL,10))));
1475 newrec.setYOffset(PADDING_TWIPS +
1476 newrec.textHeight() +
1477 (fontLeading - fontDescent));
1478 _fontHeight += pixelsToTwips(std::strtol(
1479 attloc->second.substr(1,attloc->second.length()-1).data(),
1480 NULL,10));
1481 } else if (firstchar == "-") {
1482 newrec.setTextHeight(rec.textHeight() -
1483 (pixelsToTwips(std::strtol(
1484 attloc->second.substr(1,attloc->second.length()-1).data(),
1485 NULL,10))));
1486 newrec.setYOffset(PADDING_TWIPS +
1487 newrec.textHeight() +
1488 (fontLeading - fontDescent));
1489 _fontHeight -= pixelsToTwips(std::strtol(
1490 attloc->second.substr(1,attloc->second.length()-1).data(),
1491 NULL,10));
1492 } else {
1493 newrec.setTextHeight(pixelsToTwips(std::strtol(
1494 attloc->second.data(), NULL, 10)));
1495 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1496 (fontLeading - fontDescent));
1497 _fontHeight = pixelsToTwips(std::strtol(
1498 attloc->second.data(), NULL, 10));
1501 handleChar(it, e, x, y, newrec, last_code,
1502 last_space_glyph, last_line_start_record);
1503 _fontHeight = originalsize;
1504 y = newrec.yOffset();
1506 else if (s == "IMG") {
1507 //image
1508 log_unimpl("<img> html tag in TextField");
1509 handleChar(it, e, x, y, newrec, last_code,
1510 last_space_glyph, last_line_start_record);
1512 else if (s == "I") {
1513 //italic
1514 Font* italicfont = new Font(rec.getFont()->name(),
1515 rec.getFont()->isBold(), true);
1516 newrec.setFont(italicfont);
1517 handleChar(it, e, x, y, newrec, last_code,
1518 last_space_glyph, last_line_start_record);
1519 } else if (s == "LI") {
1520 //list item (bullet)
1521 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1522 SWF::TextRecord::GlyphEntry ge;
1523 ge.index = space;
1524 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1525 newrec.addGlyph(ge, 5);
1527 // We use an asterisk instead of a bullet
1528 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1529 ge.index = bullet;
1530 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1531 newrec.addGlyph(ge);
1533 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1534 ge.index = space;
1535 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1536 newrec.addGlyph(ge, 4);
1538 handleChar(it, e, x, y, newrec, last_code,
1539 last_space_glyph, last_line_start_record);
1540 newLine(x, y, newrec, last_space_glyph,
1541 last_line_start_record, 1.0);
1543 else if (s == "SPAN") {
1544 //span
1545 log_unimpl("<span> html tag in TextField");
1546 handleChar(it, e, x, y, newrec, last_code,
1547 last_space_glyph, last_line_start_record);
1549 else if (s == "TEXTFORMAT") {
1550 log_debug("in textformat");
1551 //textformat
1552 boost::uint16_t originalblockindent = getBlockIndent();
1553 boost::uint16_t originalindent = getIndent();
1554 boost::uint16_t originalleading = getLeading();
1555 boost::uint16_t originalleftmargin = getLeftMargin();
1556 boost::uint16_t originalrightmargin = getRightMargin();
1557 std::vector<int> originaltabstops = getTabStops();
1558 attloc = attributes.find("BLOCKINDENT");
1559 if (attloc != attributes.end()) {
1560 //textformat BLOCKINDENT attribute
1561 setBlockIndent(pixelsToTwips(std::strtol(
1562 attloc->second.data(), NULL, 10)));
1563 if (newrec.xOffset() == std::max(0, originalleftmargin +
1564 originalindent + originalblockindent) + PADDING_TWIPS) {
1565 //if beginning of line, indent
1566 x = std::max(0, getLeftMargin() +
1567 getIndent() + getBlockIndent())
1568 + PADDING_TWIPS;
1569 newrec.setXOffset(x);
1572 attloc = attributes.find("INDENT");
1573 if (attloc != attributes.end()) {
1574 //textformat INDENT attribute
1575 setIndent(pixelsToTwips(std::strtol(
1576 attloc->second.data(), NULL, 10)));
1577 if (newrec.xOffset() == std::max(0, originalleftmargin +
1578 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1579 //if beginning of line, indent
1580 x = std::max(0, getLeftMargin() +
1581 getIndent() + getBlockIndent())
1582 + PADDING_TWIPS;
1583 newrec.setXOffset(x);
1586 attloc = attributes.find("LEADING");
1587 if (attloc != attributes.end()) {
1588 //textformat LEADING attribute
1589 setLeading(pixelsToTwips(std::strtol(
1590 attloc->second.data(), NULL, 10)));
1592 attloc = attributes.find("LEFTMARGIN");
1593 if (attloc != attributes.end()) {
1594 //textformat LEFTMARGIN attribute
1595 setLeftMargin(pixelsToTwips(std::strtol(
1596 attloc->second.data(), NULL, 10)));
1597 if (newrec.xOffset() == std::max(0, originalleftmargin +
1598 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1599 //if beginning of line, indent
1600 x = std::max(0, getLeftMargin() +
1601 getIndent() + getBlockIndent())
1602 + PADDING_TWIPS;
1603 newrec.setXOffset(x);
1606 attloc = attributes.find("RIGHTMARGIN");
1607 if (attloc != attributes.end()) {
1608 //textformat RIGHTMARGIN attribute
1609 setRightMargin(pixelsToTwips(std::strtol(
1610 attloc->second.data(), NULL, 10)));
1611 //FIXME:Should not apply this to this line if we are not at
1612 //beginning of line. Not sure how to do that.
1614 attloc = attributes.find("TABSTOPS");
1615 if (attloc != attributes.end()) {
1616 //textformat TABSTOPS attribute
1617 log_unimpl("html <textformat> tag tabstops attribute");
1619 handleChar(it, e, x, y, newrec, last_code,
1620 last_space_glyph, last_line_start_record);
1621 setBlockIndent(originalblockindent);
1622 setIndent(originalindent);
1623 setLeading(originalleading);
1624 setLeftMargin(originalleftmargin);
1625 setRightMargin(originalrightmargin);
1626 setTabStops(originaltabstops);
1628 else if (s == "P") {
1629 //paragraph
1630 if (_display == TEXTFORMAT_BLOCK) {
1631 handleChar(it, e, x, y, newrec, last_code,
1632 last_space_glyph,
1633 last_line_start_record);
1634 newLine(x, y, rec, last_space_glyph,
1635 last_line_start_record, 1.0);
1636 newLine(x, y, rec, last_space_glyph,
1637 last_line_start_record, 1.5);
1639 else {
1640 handleChar(it, e, x, y, newrec, last_code,
1641 last_space_glyph,
1642 last_line_start_record);
1645 else if (s == "BR" || s == "SBR") {
1646 //line break
1647 newLine(x, y, rec, last_space_glyph,
1648 last_line_start_record, 1.0);
1650 else {
1651 log_debug("<%s> tag is unsupported", s);
1652 if (!selfclosing) { //then recurse, look for closing tag
1653 handleChar(it, e, x, y, newrec, last_code,
1654 last_space_glyph, last_line_start_record);
1658 rec.setXOffset(x);
1659 rec.setYOffset(y);
1660 continue;
1662 // If HTML isn't enabled, carry on and insert the glyph.
1663 // FIXME: do we also want to be changing last_space_glyph?
1664 // ...because we are...
1665 case 32:
1666 last_space_glyph = rec.glyphs().size();
1667 // Don't break, as we still need to insert the space glyph.
1669 default:
1674 if ( password() )
1676 SWF::TextRecord::GlyphEntry ge;
1677 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1678 ge.index = bullet;
1679 ge.advance = scale * rec.getFont()->get_advance(bullet,
1680 _embedFonts);
1681 rec.addGlyph(ge);
1682 ++_glyphcount;
1683 break;
1685 // The font table holds up to 65535 glyphs. Casting
1686 // from uint32_t would, in the event that the code
1687 // is higher than 65535, result in the wrong DisplayObject
1688 // being chosen. Flash can currently only handle 16-bit
1689 // values.
1690 int index = rec.getFont()->get_glyph_index(
1691 static_cast<boost::uint16_t>(code), _embedFonts);
1693 IF_VERBOSE_MALFORMED_SWF (
1694 if (index == -1)
1696 // Missing glyph! Log the first few errors.
1697 static int s_log_count = 0;
1698 if (s_log_count < 10)
1700 s_log_count++;
1701 if (_embedFonts)
1703 log_swferror(_("TextField: missing embedded "
1704 "glyph for char %d. Make sure DisplayObject "
1705 "shapes for font %s are being exported "
1706 "into your SWF file"),
1707 code, _font->name());
1709 else
1711 log_swferror(_("TextField: missing device "
1712 "glyph for char %d. Maybe you don't have "
1713 "font '%s' installed in your system."),
1714 code, _font->name());
1718 // Drop through and use index == -1; this will display
1719 // using the empty-box glyph
1723 SWF::TextRecord::GlyphEntry ge;
1724 ge.index = index;
1725 ge.advance = scale * rec.getFont()->get_advance(index,
1726 _embedFonts);
1728 rec.addGlyph(ge);
1730 x += ge.advance;
1731 ++_glyphcount;
1735 float width = _bounds.width();
1736 if (x >= width - getRightMargin() - PADDING_TWIPS)
1738 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1739 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1740 getTarget(), _bounds);
1741 #endif
1743 // No wrap and no resize: truncate
1744 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1746 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1747 log_debug(" wordWrap=false, autoSize=none");
1748 #endif
1749 // Truncate long line, but keep expanding text box
1750 bool newlinefound = false;
1751 while (it != e)
1753 code = *it++;
1754 if (_embedFonts)
1756 x += rec.getFont()->get_kerning_adjustment(last_code,
1757 static_cast<int>(code)) * scale;
1758 last_code = code;
1760 // Expand the bounding-box to the lower-right corner
1761 // of each glyph, even if we don't display it
1762 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1763 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1764 log_debug("Text bbox expanded to %s (width: %f)",
1765 m_text_bounding_box, m_text_bounding_box.width());
1766 #endif
1768 if (code == 13 || code == 10)
1770 newlinefound = true;
1771 break;
1774 int index = rec.getFont()->get_glyph_index(
1775 static_cast<boost::uint16_t>(code), _embedFonts);
1776 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1779 if (!newlinefound) break;
1781 else if (doWordWrap()) {
1783 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1784 log_debug(" wordWrap=true");
1785 #endif
1787 // Insert newline if there's space or autosize != none
1789 // Close out this stretch of glyphs.
1790 _textRecords.push_back(rec);
1792 float previous_x = x;
1793 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1794 y += _fontHeight + leading;
1795 if (y >= _bounds.height()) {
1796 ++_maxScroll;
1799 // Start a new record on the next line.
1800 rec.clearGlyphs();
1801 rec.setXOffset(x);
1802 rec.setYOffset(y);
1804 // TODO : what if m_text_glyph_records is empty ?
1805 // Is it possible ?
1806 assert(!_textRecords.empty());
1807 SWF::TextRecord& last_line = _textRecords.back();
1809 linestartit = _line_starts.begin();
1810 linestartend = _line_starts.end();
1811 if (last_space_glyph == -1)
1813 // Pull the previous glyph down onto the
1814 // new line.
1815 if (!last_line.glyphs().empty())
1817 rec.addGlyph(last_line.glyphs().back());
1818 x += last_line.glyphs().back().advance;
1819 previous_x -= last_line.glyphs().back().advance;
1820 last_line.clearGlyphs(1);
1821 //record the new line start
1823 const size_t currentPos = _glyphcount;
1824 while (linestartit != linestartend &&
1825 *linestartit + 1 <= currentPos)
1827 linestartit++;
1829 _line_starts.insert(linestartit, currentPos);
1830 _recordStarts.push_back(currentPos);
1832 } else {
1833 // Move the previous word down onto the next line.
1835 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1837 const SWF::TextRecord::Glyphs::size_type lineSize =
1838 last_line.glyphs().size();
1839 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1840 ++i)
1842 rec.addGlyph(last_line.glyphs()[i]);
1843 x += last_line.glyphs()[i].advance;
1844 previous_x -= last_line.glyphs()[i].advance;
1846 last_line.clearGlyphs(lineSize - last_space_glyph);
1848 // record the position at the start of this line as
1849 // a line_start
1850 const size_t linestartpos = _glyphcount -
1851 rec.glyphs().size();
1853 while (linestartit < linestartend &&
1854 *linestartit < linestartpos)
1856 ++linestartit;
1858 _line_starts.insert(linestartit, linestartpos);
1859 _recordStarts.push_back(linestartpos);
1862 align_line(getTextAlignment(), last_line_start_record, previous_x);
1864 last_space_glyph = -1;
1865 last_line_start_record = _textRecords.size();
1868 else
1870 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1871 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1872 #endif
1879 TextField::getDefinitionVersion() const
1881 // TODO: work out if this correct.
1882 return get_root()->getDefinitionVersion();
1886 TextField::VariableRef
1887 TextField::parseTextVariableRef(const std::string& variableName) const
1889 VariableRef ret;
1890 ret.first = 0;
1892 #ifdef DEBUG_DYNTEXT_VARIABLES
1893 log_debug(_("VariableName: %s"), variableName);
1894 #endif
1896 /// Why isn't get_environment const again ?
1897 const as_environment& env = const_cast<TextField*>(this)->get_environment();
1899 as_object* target = getObject(env.target());
1900 if (!target) {
1901 IF_VERBOSE_MALFORMED_SWF(
1902 log_swferror(_("Current environment has no target, "
1903 "can't bind VariableName (%s) associated to "
1904 "text field. Gnash will try to register "
1905 "again on next access."), variableName);
1907 return ret;
1910 // If the variable string contains a path, we extract
1911 // the appropriate target from it and update the variable
1912 // name. We copy the string so we can assign to it if necessary.
1913 std::string parsedName = variableName;
1914 std::string path, var;
1915 if (parsePath(variableName, path, var)) {
1916 #ifdef DEBUG_DYNTEXT_VARIABLES
1917 log_debug(_("Variable text Path: %s, Var: %s"), path, var);
1918 #endif
1919 // find target for the path component
1920 // we use our parent's environment for this
1921 target = findObject(env, path);
1923 parsedName = var;
1926 if (!target) {
1927 IF_VERBOSE_MALFORMED_SWF(
1928 log_swferror(_("VariableName associated to text field refers "
1929 "to an unknown target (%s). It is possible that the "
1930 "DisplayObject will be instantiated later in the SWF "
1931 "stream. Gnash will try to register again on next "
1932 "access."), path);
1934 return ret;
1937 ret.first = target;
1938 ret.second = getURI(getVM(*object()), parsedName);
1940 return ret;
1943 void
1944 TextField::registerTextVariable()
1946 //#define DEBUG_DYNTEXT_VARIABLES 1
1948 #ifdef DEBUG_DYNTEXT_VARIABLES
1949 log_debug(_("registerTextVariable() called"));
1950 #endif
1952 if (_text_variable_registered) {
1953 return;
1956 if (_variable_name.empty()) {
1957 _text_variable_registered = true;
1958 return;
1961 VariableRef varRef = parseTextVariableRef(_variable_name);
1962 as_object* target = varRef.first;
1963 if (!target) {
1964 log_debug(_("VariableName associated to text field (%s) refer to "
1965 "an unknown target. It is possible that the DisplayObject "
1966 "will be instantiated later in the SWF stream. "
1967 "Gnash will try to register again on next access."),
1968 _variable_name);
1969 return;
1972 const ObjectURI& key = varRef.second;
1973 as_object* obj = getObject(this);
1974 const int version = getSWFVersion(*obj);
1976 // check if the VariableName already has a value,
1977 // in that case update text value
1978 as_value val;
1979 if (target->get_member(key, &val)) {
1980 // TODO: pass environment to to_string ?
1981 // as_environment& env = get_environment();
1982 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
1984 else if (_textDefined) {
1985 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
1986 target->set_member(key, newVal);
1989 MovieClip* sprite = get<MovieClip>(target);
1991 if (sprite) {
1992 // add the textfield variable to the target sprite
1993 // TODO: have set_textfield_variable take a string_table::key instead ?
1994 sprite->set_textfield_variable(key, this);
1997 _text_variable_registered = true;
2000 /// Parses an HTML tag (between < and >) and puts
2001 /// the contents into tag. Returns false if the
2002 /// tag was incomplete. The iterator is moved to after
2003 /// the closing tag or the end of the string.
2004 bool
2005 TextField::parseHTML(std::wstring& tag,
2006 std::map<std::string, std::string>& attributes,
2007 std::wstring::const_iterator& it,
2008 const std::wstring::const_iterator& e,
2009 bool& selfclosing) const
2011 while (it != e && *it != ' ') {
2012 if (*it == '/') {
2013 ++it;
2014 if (*it == '>') {
2015 ++it;
2016 selfclosing = true;
2017 return true;
2018 } else {
2019 while (it != e) {
2020 ++it;
2022 log_error("invalid html tag");
2023 return false;
2026 if (*it == '>') {
2027 ++it;
2028 return true;
2031 // Check for NULL character
2032 if (*it == 0) {
2033 log_error("found NULL character in htmlText");
2034 return false;
2036 tag.push_back(std::toupper(*it));
2037 ++it;
2039 while (it != e && *it == ' ') {
2040 ++it; //skip over spaces
2042 if (*it == '>') {
2043 ++it;
2044 return true;
2046 if (*it == '/') {
2047 ++it;
2048 if (*it == '>') {
2049 ++it;
2050 selfclosing = true;
2051 return true;
2052 } else {
2053 while (it != e) {
2054 ++it;
2056 log_error("invalid html tag");
2057 return false;
2061 std::string attname;
2062 std::string attvalue;
2064 //attributes
2065 while (it != e && *it != '>') {
2066 while (it != e && *it != '=' && *it != ' ') {
2068 if (*it == 0) {
2069 log_error("found NULL character in htmlText");
2070 return false;
2072 if (*it == '>') {
2073 log_error("malformed HTML tag, invalid attribute name");
2074 while (it != e) {
2075 ++it;
2077 return false;
2080 attname.push_back(std::toupper(*it));
2081 ++it;
2083 while (it != e && (*it == ' ' || *it == '=')) {
2084 ++it; //skip over spaces and '='
2087 if (it == e) return false;
2088 const char q = *it;
2089 if (q != '"' && q != '\'') {
2090 // This is not an attribute.
2091 while (it != e) ++it;
2092 return false;
2095 // Advance past attribute opener
2096 ++it;
2097 while (it != e && *it != q) {
2099 if (*it == 0) {
2100 log_error("found NULL character in htmlText");
2101 return false;
2104 attvalue.push_back(std::toupper(*it));
2105 ++it;
2108 if (it == e) return false;
2110 if (*it != q) {
2111 while (it != e) ++it;
2112 return false;
2115 // Skip attribute closer.
2116 ++it;
2118 attributes.insert(std::make_pair(attname, attvalue));
2119 attname.clear();
2120 attvalue.clear();
2122 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2123 log_error("malformed HTML tag, invalid attribute value");
2124 while (it != e) {
2125 ++it;
2127 return false;
2129 if (*it == ' ') {
2130 while (it != e && *it == ' ') {
2131 ++it; //skip over spaces
2134 if (*it == '>') {
2135 ++it;
2136 return true;
2137 } else if (*it == '/') {
2138 ++it;
2139 if (*it == '>') {
2140 ++it;
2141 selfclosing = true;
2142 return true;
2143 } else {
2144 while (it != e) {
2145 ++it;
2147 log_error("invalid html tag");
2148 return false;
2153 #ifdef GNASH_DEBUG_TEXTFIELDS
2154 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2155 #endif
2156 log_error("I declare this a HTML syntax error");
2157 return false; //since we did not return already, must be malformed...?
2160 void
2161 TextField::set_variable_name(const std::string& newname)
2163 if (newname != _variable_name) {
2164 _variable_name = newname;
2166 // The name was empty or undefined, so there's nothing more to do.
2167 if (_variable_name.empty()) return;
2169 _text_variable_registered = false;
2171 #ifdef DEBUG_DYNTEXT_VARIABLES
2172 log_debug("Calling updateText after change of variable name");
2173 #endif
2175 // Use the original definition text if this isn't dynamically
2176 // created.
2177 if (_tag) updateText(_tag->defaultText());
2179 #ifdef DEBUG_DYNTEXT_VARIABLES
2180 log_debug("Calling registerTextVariable after change of variable "
2181 "name and updateText call");
2182 #endif
2183 registerTextVariable();
2187 bool
2188 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2190 const SWFMatrix wm = getWorldMatrix(*this).invert();
2191 point lp(x, y);
2192 wm.transform(lp);
2193 return _bounds.point_test(lp.x, lp.y);
2196 bool
2197 TextField::getDrawBorder() const
2199 return _drawBorder;
2202 void
2203 TextField::setDrawBorder(bool val)
2205 if (_drawBorder != val) {
2206 set_invalidated();
2207 _drawBorder = val;
2211 rgba
2212 TextField::getBorderColor() const
2214 return _borderColor;
2217 void
2218 TextField::setBorderColor(const rgba& col)
2220 if (_borderColor != col) {
2221 set_invalidated();
2222 _borderColor = col;
2226 bool
2227 TextField::getDrawBackground() const
2229 return _drawBackground;
2232 void
2233 TextField::setDrawBackground(bool val)
2235 if (_drawBackground != val) {
2236 set_invalidated();
2237 _drawBackground = val;
2241 rgba
2242 TextField::getBackgroundColor() const
2244 return _backgroundColor;
2247 void
2248 TextField::setBackgroundColor(const rgba& col)
2250 if (_backgroundColor != col) {
2251 set_invalidated();
2252 _backgroundColor = col;
2256 void
2257 TextField::setTextColor(const rgba& col)
2259 if (_textColor != col) {
2261 set_invalidated();
2262 _textColor = col;
2263 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2264 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2268 void
2269 TextField::setEmbedFonts(bool use)
2271 if (_embedFonts != use) {
2272 set_invalidated();
2273 _embedFonts=use;
2274 format_text();
2278 void
2279 TextField::setWordWrap(bool wrap)
2281 if (_wordWrap != wrap) {
2282 set_invalidated();
2283 _wordWrap = wrap;
2284 format_text();
2288 void
2289 TextField::setLeading(boost::int16_t h)
2291 if (_leading != h) {
2292 set_invalidated();
2293 _leading = h;
2297 void
2298 TextField::setUnderlined(bool v)
2300 if (_underlined != v) {
2301 set_invalidated();
2302 _underlined = v;
2306 void
2307 TextField::setBullet(bool b)
2309 if (_bullet != b) {
2310 _bullet = b;
2314 void
2315 TextField::setTabStops(const std::vector<int>& tabStops)
2317 _tabStops.resize(tabStops.size());
2319 for (size_t i = 0; i < tabStops.size(); i ++) {
2320 _tabStops[i] = pixelsToTwips(tabStops[i]);
2323 set_invalidated();
2326 void
2327 TextField::setURL(std::string url)
2329 if (_url != url) {
2330 set_invalidated();
2331 _url = url;
2335 void
2336 TextField::setTarget(std::string target)
2338 if (_target != target) {
2339 set_invalidated();
2340 _target = target;
2344 void
2345 TextField::setDisplay(TextFormatDisplay display)
2347 if (_display != display) {
2348 set_invalidated();
2349 _display = display;
2353 void
2354 TextField::setAlignment(TextAlignment h)
2356 if (_alignment != h) {
2357 set_invalidated();
2358 _alignment = h;
2362 void
2363 TextField::setIndent(boost::uint16_t h)
2365 if (_indent != h) {
2366 set_invalidated();
2367 _indent = h;
2371 void
2372 TextField::setBlockIndent(boost::uint16_t h)
2374 if (_blockIndent != h) {
2375 set_invalidated();
2376 _blockIndent = h;
2380 void
2381 TextField::setRightMargin(boost::uint16_t h)
2383 if (_rightMargin != h) {
2384 set_invalidated();
2385 _rightMargin = h;
2389 void
2390 TextField::setLeftMargin(boost::uint16_t h)
2392 if (_leftMargin != h) {
2393 set_invalidated();
2394 _leftMargin = h;
2398 void
2399 TextField::setFontHeight(boost::uint16_t h)
2401 if (_fontHeight != h) {
2402 set_invalidated();
2403 _fontHeight = h;
2408 TextField::TypeValue
2409 TextField::parseTypeValue(const std::string& val)
2411 StringNoCaseEqual cmp;
2413 if (cmp(val, "input")) return typeInput;
2414 if (cmp(val, "dynamic")) return typeDynamic;
2415 return typeInvalid;
2419 const char*
2420 TextField::typeValueName(TypeValue val)
2422 switch (val) {
2423 case typeInput:
2424 //log_debug("typeInput returned as 'input'");
2425 return "input";
2426 case typeDynamic:
2427 //log_debug("typeDynamic returned as 'dynamic'");
2428 return "dynamic";
2429 default:
2430 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2431 return "invalid";
2436 void
2437 TextField::setAutoSize(AutoSize val)
2439 if (val == _autoSize) return;
2441 set_invalidated();
2442 _autoSize = val;
2443 format_text();
2446 TextField::TextAlignment
2447 TextField::getTextAlignment()
2449 TextAlignment textAlignment = getAlignment();
2451 switch (_autoSize) {
2452 case AUTOSIZE_CENTER:
2453 textAlignment = ALIGN_CENTER;
2454 break;
2455 case AUTOSIZE_LEFT:
2456 textAlignment = ALIGN_LEFT;
2457 break;
2458 case AUTOSIZE_RIGHT:
2459 textAlignment = ALIGN_RIGHT;
2460 break;
2461 default:
2462 // Leave it as it was.
2463 break;
2466 return textAlignment;
2469 void
2470 TextField::onChanged()
2472 as_object* obj = getObject(this);
2473 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2476 /// This is called by movie_root when focus is applied to this TextField.
2478 /// The return value is true if the TextField can receive focus.
2479 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2480 /// handle focus.
2481 bool
2482 TextField::handleFocus()
2484 set_invalidated();
2486 /// Select the entire text on focus.
2487 setSelection(0, _text.length());
2489 m_has_focus = true;
2491 m_cursor = _text.size();
2492 format_text();
2493 return true;
2496 /// This is called by movie_root when focus is removed from the
2497 /// current TextField.
2498 void
2499 TextField::killFocus()
2501 if (!m_has_focus) return;
2502 set_invalidated();
2503 m_has_focus = false;
2504 format_text(); // is this needed ?
2507 void
2508 TextField::setWidth(double newwidth)
2510 const SWFRect& bounds = getBounds();
2511 _bounds.set_to_rect(bounds.get_x_min(),
2512 bounds.get_y_min(),
2513 bounds.get_x_min() + newwidth,
2514 bounds.get_y_max());
2517 void
2518 TextField::setHeight(double newheight)
2520 const SWFRect& bounds = getBounds();
2521 _bounds.set_to_rect(bounds.get_x_min(),
2522 bounds.get_y_min(),
2523 bounds.get_x_max(),
2524 bounds.get_y_min() + newheight);
2527 } // namespace gnash
2530 // Local Variables:
2531 // mode: C++
2532 // indent-tabs-mode: t
2533 // End: