Flag an error if the FB gui is configured without the rawfb device. For #108015.
[gnash.git] / libcore / TextField.cpp
blobe943af32361e601f2d61060d316af9cccdc07afe
1 // TextField.cpp: User-editable text regions, 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.
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())
125 assert(object);
127 // WARNING! remember to set the font *before* setting text value!
128 boost::intrusive_ptr<const Font> f = def.getFont();
129 if (!f) f = fontlib::get_default_font();
130 setFont(f);
132 const int version = getSWFVersion(*object);
134 // set default text *before* calling registerTextVariable
135 // (if the textvariable already exist and has a value
136 // the text will be replaced with it)
137 if (_textDefined) {
138 setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
141 init();
145 TextField::TextField(as_object* object, DisplayObject* parent,
146 const SWFRect& bounds)
148 InteractiveObject(object, parent),
149 _url(""),
150 _target(""),
151 _display(),
152 _tabStops(),
153 _backgroundColor(255,255,255,255),
154 _borderColor(0, 0, 0, 255),
155 _textColor(0, 0, 0, 255),
156 _alignment(ALIGN_LEFT),
157 _font(0),
158 m_cursor(0u),
159 _glyphcount(0u),
160 _scroll(0u),
161 _maxScroll(1u),
162 _hScroll(0u),
163 _maxHScroll(0u),
164 _bottomScroll(0u),
165 _linesindisplay(0u),
166 _maxChars(0),
167 _autoSize(AUTOSIZE_NONE),
168 _type(typeDynamic),
169 _bounds(bounds),
170 _selection(0, 0),
171 _leading(0),
172 _indent(0),
173 _blockIndent(0),
174 _leftMargin(0),
175 _rightMargin(0),
176 _fontHeight(12 * 20),
177 _textDefined(false),
178 _restrictDefined(false),
179 _underlined(false),
180 _bullet(false),
181 m_has_focus(false),
182 _multiline(false),
183 _password(false),
184 _text_variable_registered(false),
185 _drawBackground(false),
186 _drawBorder(false),
187 _embedFonts(false),
188 _wordWrap(false),
189 _html(false),
190 _selectable(true)
192 assert(object);
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> line = boost::assign::list_of
267 (point(x, y))
268 (point(x, y + h));
270 renderer.drawLine(line, rgba(0, 0, 0, 255), mat);
273 size_t
274 TextField::cursorRecord()
276 if (_textRecords.empty()) return 0;
278 size_t i = 0;
280 while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
281 ++i;
283 // TODO: it seems like this could return (size_t) -1, but there's no
284 // evidence this is allowed or handled.
285 return i - 1;
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 boost::int32_t xmin = _bounds.get_x_min();
307 boost::int32_t xmax = _bounds.get_x_max();
308 boost::int32_t ymin = _bounds.get_y_min();
309 boost::int32_t ymax = _bounds.get_y_max();
311 const std::vector<point> coords = boost::assign::list_of
312 (point(xmin, ymin))
313 (point(xmax, ymin))
314 (point(xmax, ymax))
315 (point(xmin, ymax));
317 rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
318 rgba backgroundColor = drawBackground ? getBackgroundColor() :
319 rgba(0,0,0,0);
321 SWFCxForm cx = xform.colorTransform;
323 if (drawBorder) borderColor = cx.transform(borderColor);
325 if (drawBackground) backgroundColor = cx.transform(backgroundColor);
327 #ifdef GNASH_DEBUG_TEXTFIELDS
328 log_debug("rendering a Pol composed by corners %s", _bounds);
329 #endif
331 renderer.draw_poly(coords, backgroundColor,
332 borderColor, xform.matrix, true);
336 // Draw our actual text.
337 // Using a SWFMatrix to translate to def bounds seems an hack to me.
338 // A cleaner implementation is likely correctly setting the
339 // _xOffset and _yOffset memebers in glyph records.
340 // Anyway, see bug #17954 for a testcase.
341 if (!_bounds.is_null()) {
342 xform.matrix.concatenate_translation(_bounds.get_x_min(),
343 _bounds.get_y_min());
346 _displayRecords.clear();
347 // TODO: work out how leading should be implemented.
348 const float fontLeading = 0;
350 //offset the lines
351 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
352 size_t recordline;
353 for (size_t i = 0; i < _textRecords.size(); ++i) {
354 recordline = 0;
355 //find the line the record is on
356 while (recordline < _line_starts.size() &&
357 _line_starts[recordline] <= _recordStarts[i]) {
358 ++recordline;
360 //offset the line
361 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
362 //add the lines we want to the display record
363 if (_textRecords[i].yOffset() > 0 &&
364 _textRecords[i].yOffset() < _bounds.height()) {
365 _displayRecords.push_back(_textRecords[i]);
369 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
370 _embedFonts);
372 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
374 clear_invalidated();
378 void
379 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
381 if (!force && !invalidated()) return; // no need to redraw
383 ranges.add(m_old_invalidated_ranges);
385 const SWFMatrix& wm = getWorldMatrix(*this);
387 SWFRect bounds = getBounds();
388 bounds.expand_to_rect(m_text_bounding_box);
389 wm.transform(bounds);
390 ranges.add(bounds.getRange());
393 void
394 TextField::setRestrict(const std::string& restrict)
396 _restrictDefined = true;
398 std::string::const_iterator rit = restrict.begin();
399 std::string::const_iterator re = restrict.end();
400 std::set<wchar_t>::const_iterator locate;
402 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
403 for (unsigned int i = 0; i <= 255; ++i) {
404 _restrictedchars.insert(char(i));
406 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
407 _restrictedchars.clear();
410 while (rit != re) {
411 while (rit != re && *rit != '^') { //This loop allows chars
412 if (*rit == '-') {
413 log_error(_("invalid restrict string"));
414 return;
415 } else if (*(rit+1) == '-') {
416 if (re - (rit+2) != 0) {
417 unsigned int q = *(rit+2);
418 for (unsigned int p = *rit; p <= q; (++p)){
419 _restrictedchars.insert(char(p));
421 rit += 3;
422 } else {
423 log_error(_("invalid restrict string"));
424 return;
426 } else if (*rit == '\\') {
427 ++rit;
428 _restrictedchars.insert(*rit);
429 ++rit;
430 } else {
431 _restrictedchars.insert(*rit);
432 ++rit;
435 if (rit != re) {
436 ++rit;
438 while (rit != re && *rit != '^') { //This loop restricts chars
439 locate = _restrictedchars.find(*rit);
440 if (*rit == '-') {
441 log_error(_("invalid restrict string"));
442 return;
443 } else if (*(rit+1) == '-') {
444 if (re - (rit+2) != 0) {
445 unsigned int q = *(rit+2);
446 for (unsigned int p = *rit; p <= q; ++p){
447 locate = _restrictedchars.find(p);
448 if(locate != _restrictedchars.end()) {
449 _restrictedchars.erase(locate);
452 ++rit;
453 ++rit;
454 ++rit;
455 } else {
456 log_error(_("invalid restrict string"));
457 return;
459 } else if (*rit == '\\') {
460 ++rit;
461 locate = _restrictedchars.find(*rit);
462 if(locate != _restrictedchars.end()) {
463 _restrictedchars.erase(locate);
465 ++rit;
466 } else {
467 if(locate != _restrictedchars.end()) {
468 _restrictedchars.erase(locate);
470 ++rit;
473 if (rit != re) {
474 ++rit;
477 _restrict = restrict;
480 void
481 TextField::replaceSelection(const std::string& replace)
483 const int version = getSWFVersion(*getObject(this));
484 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
486 assert(_selection.second >= _selection.first);
487 assert(_selection.second <= _text.size());
488 assert(_selection.first <= _text.size());
490 // If the text has changed but the selection hasn't, make sure we
491 // don't access it out of bounds.
492 const size_t start = _selection.first;
493 const size_t end = _selection.second;
495 const size_t replaceLength = wstr.size();
497 _text.replace(start, end - start, wstr);
498 _selection = std::make_pair(start + replaceLength, start + replaceLength);
501 void
502 TextField::setSelection(int start, int end)
504 if (_text.empty()) {
505 _selection = std::make_pair(0, 0);
506 return;
509 const size_t textLength = _text.size();
511 if (start < 0) start = 0;
512 else start = std::min<size_t>(start, textLength);
514 if (end < 0) end = 0;
515 else end = std::min<size_t>(end, textLength);
517 // The cursor position is always set to the end value, even if the
518 // two values are swapped to obtain the selection. Equal values are
519 // fine.
520 m_cursor = end;
521 if (start > end) std::swap(start, end);
523 _selection = std::make_pair(start, end);
526 void
527 TextField::keyInput(key::code c)
529 // c is the unique gnash::key::code for a DisplayObject/key.
530 // The maximum value is about 265, including function keys.
531 // It seems that typing in DisplayObjects outside the Latin-1 set
532 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
533 // is not supported, though a much greater number UTF-8 codes can be
534 // stored and displayed. See utf.h for more information.
535 // This is a limit on the number of key codes, not on the
536 // capacity of strings.
539 setHtml(false); //editable html fields are not yet implemented
540 std::wstring s = _text;
542 // maybe _text is changed in ActionScript
543 m_cursor = std::min<size_t>(m_cursor, _text.size());
545 size_t cur_cursor = m_cursor;
546 size_t previouslinesize = 0;
547 size_t nextlinesize = 0;
548 size_t manylines = _line_starts.size();
549 LineStarts::iterator linestartit = _line_starts.begin();
550 LineStarts::const_iterator linestartend = _line_starts.end();
552 switch (c) {
553 case key::BACKSPACE:
554 if (isReadOnly()) return;
555 if (m_cursor > 0)
557 s.erase(m_cursor - 1, 1);
558 m_cursor--;
559 setTextValue(s);
561 break;
563 case key::DELETEKEY:
564 if (isReadOnly()) return;
565 if (_glyphcount > m_cursor)
567 s.erase(m_cursor, 1);
568 setTextValue(s);
570 break;
572 case key::INSERT: // TODO
573 if (isReadOnly()) return;
574 break;
576 case key::HOME:
577 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
578 cur_cursor = *linestartit;
579 ++linestartit;
581 m_cursor = cur_cursor;
582 break;
584 case key::PGUP:
585 // if going a page up is too far...
586 if(_scroll < _linesindisplay) {
587 _scroll = 0;
588 m_cursor = 0;
589 } else { // go a page up
590 _scroll -= _linesindisplay;
591 m_cursor = _line_starts[_scroll];
593 scrollLines();
594 break;
596 case key::UP:
597 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
598 cur_cursor = *linestartit;
599 ++linestartit;
601 //if there is no previous line
602 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
603 m_cursor = 0;
604 break;
606 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
607 //if the previous line is smaller
608 if (m_cursor - cur_cursor > previouslinesize) {
609 m_cursor = *(--(--linestartit)) + previouslinesize;
610 } else {
611 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
613 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
614 --_scroll;
616 scrollLines();
617 break;
619 case key::END:
620 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
621 ++linestartit;
623 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
624 break;
626 case key::PGDN:
627 //if going another page down is too far...
628 if(_scroll + _linesindisplay >= manylines) {
629 if(manylines - _linesindisplay <= 0) {
630 _scroll = 0;
631 } else {
632 _scroll = manylines - _linesindisplay;
634 if(m_cursor < _line_starts[_scroll-1]) {
635 m_cursor = _line_starts[_scroll-1];
636 } else {
637 m_cursor = _text.size();
639 } else { //go a page down
640 _scroll += _linesindisplay;
641 m_cursor = _line_starts[_scroll];
643 scrollLines();
644 break;
646 case key::DOWN:
648 while (linestartit < linestartend &&
649 *linestartit <= m_cursor ) {
650 cur_cursor = *linestartit;
651 ++linestartit;
654 // linestartit should never be before _line_starts.begin()
655 const size_t currentLine = linestartit -
656 _line_starts.begin();
658 //if there is no next line
659 if (currentLine >= manylines ) {
660 m_cursor = _text.size();
661 break;
663 nextlinesize = _textRecords[currentLine].glyphs().size();
665 //if the next line is smaller
666 if (m_cursor - cur_cursor > nextlinesize) {
667 m_cursor = *linestartit + nextlinesize;
668 } else {
669 //put the cursor at the same character distance
670 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
672 if (_line_starts.size() > _linesindisplay &&
673 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
674 ++_scroll;
676 scrollLines();
677 break;
680 case key::LEFT:
681 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
682 break;
684 case key::RIGHT:
685 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
686 _glyphcount;
687 break;
689 case key::ENTER:
690 if (isReadOnly()) return;
691 if (!multiline()) break;
693 default:
695 if (maxChars() != 0) {
696 if (_maxChars <= _glyphcount) {
697 break;
701 if (isReadOnly()) return;
702 wchar_t t = static_cast<wchar_t>(
703 gnash::key::codeMap[c][key::ASCII]);
704 if (t != 0) {
706 if (!_restrictDefined) {
707 // Insert one copy of the character
708 // at the cursor position.
709 s.insert(m_cursor, 1, t);
710 m_cursor++;
711 } else if (_restrictedchars.count(t)) {
712 // Insert one copy of the character
713 // at the cursor position.
714 s.insert(m_cursor, 1, t);
715 m_cursor++;
716 } else if (_restrictedchars.count(tolower(t))) {
717 // restrict substitutes the opposite case
718 s.insert(m_cursor, 1, tolower(t));
719 m_cursor++;
720 } else if (_restrictedchars.count(toupper(t))) {
721 // restrict substitutes the opposite case
722 s.insert(m_cursor, 1, toupper(t));
723 m_cursor++;
726 setTextValue(s);
728 onChanged();
729 set_invalidated();
732 void
733 TextField::mouseEvent(const event_id& ev)
735 switch (ev.id())
737 case event_id::PRESS:
739 movie_root& root = stage();
740 boost::int32_t x_mouse, y_mouse;
741 boost::tie(x_mouse, y_mouse) = root.mousePosition();
743 SWFMatrix m = getMatrix(*this);
745 x_mouse -= m.get_x_translation();
746 y_mouse -= m.get_y_translation();
748 SWF::TextRecord rec;
750 for (size_t i=0; i < _textRecords.size(); ++i) {
751 if ((x_mouse > _textRecords[i].xOffset()) &&
752 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
753 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
754 (y_mouse < _textRecords[i].yOffset())) {
755 rec = _textRecords[i];
756 break;
760 if (!rec.getURL().empty()) {
761 root.getURL(rec.getURL(), rec.getTarget(), "",
762 MovieClip::METHOD_NONE);
765 break;
767 default:
768 return;
772 InteractiveObject*
773 TextField::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
775 if (!visible()) return 0;
777 // Not selectable, so don't catch mouse events!
778 if (!_selectable) return 0;
780 SWFMatrix m = getMatrix(*this);
781 point p(x, y);
782 m.invert().transform(p);
784 if (_bounds.point_test(p.x, p.y)) return this;
786 return 0;
789 void
790 TextField::updateText(const std::string& str)
792 const int version = getSWFVersion(*getObject(this));
793 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
794 updateText(wstr);
797 void
798 TextField::updateText(const std::wstring& wstr)
800 _textDefined = true;
801 if (_text == wstr) return;
803 set_invalidated();
805 _text = wstr;
807 _selection.first = std::min(_selection.first, _text.size());
808 _selection.second = std::min(_selection.second, _text.size());
810 format_text();
813 void
814 TextField::updateHtmlText(const std::wstring& wstr)
816 if (_htmlText == wstr) return;
818 set_invalidated();
820 _htmlText = wstr;
821 format_text();
824 void
825 TextField::setTextValue(const std::wstring& wstr)
827 updateHtmlText(wstr);
828 updateText(wstr);
830 if (!_variable_name.empty() && _text_variable_registered) {
831 // TODO: notify MovieClip if we have a variable name !
832 VariableRef ref = parseTextVariableRef(_variable_name);
833 as_object* tgt = ref.first;
834 if (tgt) {
835 const int version = getSWFVersion(*getObject(this));
836 // we shouldn't truncate, right?
837 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
838 version));
840 else {
841 // nothing to do (too early ?)
842 log_debug("setTextValue: variable name %s points to a non-existent"
843 "target, I guess we would not be registered if this was"
844 "true, or the sprite we've registered our variable name"
845 "has been unloaded", _variable_name);
850 std::string
851 TextField::get_text_value() const
853 // we need the const_cast here because registerTextVariable
854 // *might* change our text value, calling the non-const
855 // setTextValue().
856 // This happens if the TextVariable has not been already registered
857 // and during registration comes out to name an existing variable
858 // with a pre-existing value.
859 const_cast<TextField*>(this)->registerTextVariable();
861 const int version = getSWFVersion(*getObject(this));
863 return utf8::encodeCanonicalString(_text, version);
866 std::string
867 TextField::get_htmltext_value() const
869 const_cast<TextField*>(this)->registerTextVariable();
870 const int version = getSWFVersion(*getObject(this));
871 return utf8::encodeCanonicalString(_htmlText, version);
874 void
875 TextField::setTextFormat(TextFormat_as& tf)
877 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
878 //This is just so we can set individual variables without having to call format_text()
879 //This calls format_text() at the end of setting TextFormat
880 if (tf.align()) setAlignment(*tf.align());
881 if (tf.size()) setFontHeight(*tf.size()); // keep twips
882 if (tf.indent()) setIndent(*tf.indent());
883 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
884 if (tf.leading()) setLeading(*tf.leading());
885 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
886 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
887 if (tf.color()) setTextColor(*tf.color());
888 if (tf.underlined()) setUnderlined(*tf.underlined());
889 if (tf.bullet()) setBullet(*tf.bullet());
890 setDisplay(tf.display());
891 if (tf.tabStops()) setTabStops(*tf.tabStops());
893 // NEED TO IMPLEMENT THESE TWO
894 if (tf.url()) setURL(*tf.url());
895 if (tf.target()) setTarget(*tf.target());
897 format_text();
900 float
901 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
903 float width = _bounds.width();
904 float right_margin = getRightMargin();
906 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
908 if (extra_space <= 0.0f) {
909 #ifdef GNASH_DEBUG_TEXTFIELDS
910 log_debug("TextField text doesn't fit in its boundaries: "
911 "width %g, margin %g - nothing to align",
912 width, right_margin);
913 #endif
914 return 0.0f;
917 float shift_right = 0.0f;
919 switch (align) {
920 case ALIGN_LEFT:
921 // Nothing to do; already aligned left.
922 return 0.0f;
923 case ALIGN_CENTER:
924 // Distribute the space evenly on both sides.
925 shift_right = extra_space / 2;
926 break;
927 case ALIGN_RIGHT:
928 // Shift all the way to the right.
929 shift_right = extra_space;
930 break;
931 case ALIGN_JUSTIFY:
932 // What should we do here?
933 break;
936 // Shift the beginnings of the records on this line.
937 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
938 SWF::TextRecord& rec = _textRecords[i];
939 rec.setXOffset(rec.xOffset() + shift_right);
941 return shift_right;
944 boost::intrusive_ptr<const Font>
945 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
947 if (newfont == _font) return _font;
949 boost::intrusive_ptr<const Font> oldfont = _font;
950 set_invalidated();
951 _font = newfont;
952 format_text();
953 return oldfont;
957 void
958 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
960 // tab (ASCII HT)
961 const int space = 32;
962 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
963 if (index == -1) {
964 IF_VERBOSE_MALFORMED_SWF (
965 log_error(_("TextField: missing glyph for space char (needed "
966 "for TAB). Make sure DisplayObject shapes for font "
967 "%s are being exported into your SWF file."),
968 rec.getFont()->name());
971 else {
972 // TODO: why is there a copy of the vector?
973 std::vector<int> tabStops = _tabStops;
975 std::sort(_tabStops.begin(), _tabStops.end());
977 if (!_tabStops.empty()) {
978 int tab = _tabStops.back() + 1;
980 for (size_t i = 0; i < tabStops.size(); ++i) {
981 if (tabStops[i] > x) {
982 if((tabStops[i] - x) < tab) {
983 tab = tabStops[i] - x;
989 // This is necessary in case the number of tabs in the text
990 // are more than the actual number of tabStops inside the
991 // vector
992 if (tab != _tabStops.back() + 1) {
993 SWF::TextRecord::GlyphEntry ge;
994 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
995 ge.advance = tab;
996 rec.addGlyph(ge);
997 x+=ge.advance;
1000 else {
1001 SWF::TextRecord::GlyphEntry ge;
1002 ge.index = index;
1003 ge.advance = scale * rec.getFont()->get_advance(index,
1004 _embedFonts);
1006 const int tabstop = 4;
1007 rec.addGlyph(ge, tabstop);
1008 x += ge.advance * tabstop;
1013 void
1014 TextField::format_text()
1016 _textRecords.clear();
1017 _line_starts.clear();
1018 _recordStarts.clear();
1019 _glyphcount = 0;
1021 _recordStarts.push_back(0);
1023 // nothing more to do if text is empty
1024 if (_text.empty()) {
1025 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1026 // not sure we should...
1027 reset_bounding_box(0, 0);
1028 return;
1031 AutoSize autoSize = getAutoSize();
1032 if (autoSize != AUTOSIZE_NONE) {
1033 // When doing WordWrap we don't want to change
1034 // the boundaries. See bug #24348
1035 if (!doWordWrap()) {
1036 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1040 // FIXME: I don't think we should query the definition
1041 // to find the appropriate font to use, as ActionScript
1042 // code should be able to change the font of a TextField
1043 if (!_font) {
1044 log_error(_("No font for TextField!"));
1045 return;
1048 boost::uint16_t fontHeight = getFontHeight();
1049 const float scale = fontHeight /
1050 static_cast<float>(_font->unitsPerEM(_embedFonts));
1052 // TODO: work out how leading affects things.
1053 const float fontLeading = 0;
1055 const boost::uint16_t leftMargin = getLeftMargin();
1056 const boost::uint16_t indent = getIndent();
1057 const boost::uint16_t blockIndent = getBlockIndent();
1058 const bool underlined = getUnderlined();
1060 /// Remember the current bounds for autosize.
1061 SWFRect oldBounds(_bounds);
1063 SWF::TextRecord rec; // one to work on
1064 rec.setFont(_font.get());
1065 rec.setUnderline(underlined);
1066 rec.setColor(getTextColor());
1067 rec.setXOffset(PADDING_TWIPS +
1068 std::max(0, leftMargin + indent + blockIndent));
1069 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1070 rec.setTextHeight(fontHeight);
1072 // create in textrecord.h
1073 rec.setURL(_url);
1074 rec.setTarget(_target);
1076 // BULLET CASE:
1078 // First, we indent 10 spaces, and then place the bullet
1079 // character (in this case, an asterisk), then we pad it
1080 // again with 10 spaces
1081 // Note: this works only for additional lines of a
1082 // bulleted list, so that is why there is a bullet format
1083 // in the beginning of format_text()
1084 if (_bullet) {
1085 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1087 SWF::TextRecord::GlyphEntry ge;
1088 ge.index = space;
1089 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1090 rec.addGlyph(ge, 5);
1092 // We use an asterisk instead of a bullet
1093 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1094 ge.index = bullet;
1095 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1096 rec.addGlyph(ge);
1098 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1099 ge.index = space;
1100 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1101 rec.addGlyph(ge, 4);
1104 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1105 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1107 // Start the bbox at the upper-left corner of the first glyph.
1108 //reset_bounding_box(x, y + fontHeight);
1110 int last_code = -1; // only used if _embedFonts
1111 int last_space_glyph = -1;
1112 size_t last_line_start_record = 0;
1114 _line_starts.push_back(0);
1116 // String iterators are very sensitive to
1117 // potential changes to the string (to allow for copy-on-write).
1118 // So there must be no external changes to the string or
1119 // calls to most non-const member functions during this loop.
1120 // Especially not c_str() or data().
1121 std::wstring::const_iterator it = _text.begin();
1122 const std::wstring::const_iterator e = _text.end();
1124 ///handleChar takes care of placing the glyphs
1125 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1126 last_line_start_record);
1128 // Expand bounding box to include the whole text (if autoSize and wordWrap
1129 // is not in operation.
1130 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1132 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1134 if (_autoSize == AUTOSIZE_RIGHT) {
1135 /// Autosize right expands from the previous right margin.
1136 SWFMatrix m;
1138 m.set_x_translation(oldBounds.get_x_max() - _bounds.width());
1139 m.transform(_bounds);
1141 else if (_autoSize == AUTOSIZE_CENTER) {
1142 // Autosize center expands from the previous center.
1143 SWFMatrix m;
1144 m.set_x_translation(oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1145 _bounds.width() / 2.0);
1146 m.transform(_bounds);
1150 // Add the last line to our output.
1151 _textRecords.push_back(rec);
1153 // align the last (or single) line
1154 align_line(getTextAlignment(), last_line_start_record, x);
1156 scrollLines();
1158 set_invalidated(); //redraw
1162 void
1163 TextField::scrollLines()
1165 boost::uint16_t fontHeight = getFontHeight();
1166 const float fontLeading = 0;
1168 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1169 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1170 size_t manylines = _line_starts.size();
1171 size_t lastvisibleline = _scroll + _linesindisplay;
1172 size_t line = 0;
1174 // If there aren't as many lines as we have scrolled, display the
1175 // end of the text.
1176 if (manylines < _scroll) {
1177 _scroll = manylines - _linesindisplay;
1178 return;
1181 // which line is the cursor on?
1182 while (line < manylines && _line_starts[line] <= m_cursor) {
1183 ++line;
1186 if (manylines - _scroll <= _linesindisplay) {
1187 // This is for if we delete a line
1188 if (manylines < _linesindisplay) _scroll = 0;
1189 else {
1190 _scroll = manylines - _linesindisplay;
1192 } else if (line < _scroll) {
1193 //if we are at a higher position, scroll the lines down
1194 _scroll -= _scroll - line;
1195 } else if (manylines > _scroll + _linesindisplay) {
1196 //if we are at a lower position, scroll the lines up
1197 if (line >= (_scroll+_linesindisplay)) {
1198 _scroll += line - (lastvisibleline);
1204 void
1205 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1206 SWF::TextRecord& rec, int& last_space_glyph,
1207 LineStarts::value_type& last_line_start_record, float div)
1209 // newline.
1210 LineStarts::iterator linestartit = _line_starts.begin();
1211 LineStarts::const_iterator linestartend = _line_starts.end();
1213 // TODO: work out how leading affects things.
1214 const float leading = 0;
1216 // Close out this stretch of glyphs.
1217 ++_glyphcount;
1218 _textRecords.push_back(rec);
1219 _recordStarts.push_back(_glyphcount);
1220 align_line(getTextAlignment(), last_line_start_record, x);
1222 // Expand bounding box to include last column of text ...
1223 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1224 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1227 // new paragraphs get the indent.
1228 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1229 PADDING_TWIPS;
1230 y += div * (getFontHeight() + leading);
1231 if (y >= _bounds.height()) {
1232 ++_maxScroll;
1235 // Start a new record on the next line. Other properties of the
1236 // TextRecord should be left unchanged.
1237 rec.clearGlyphs();
1238 rec.setXOffset(x);
1239 rec.setYOffset(y);
1241 last_space_glyph = -1;
1242 last_line_start_record = _textRecords.size();
1244 linestartit = _line_starts.begin();
1245 linestartend = _line_starts.end();
1246 //Fit a line_start in the correct place
1247 const size_t currentPos = _glyphcount;
1249 while (linestartit < linestartend && *linestartit < currentPos)
1251 ++linestartit;
1253 _line_starts.insert(linestartit, currentPos);
1255 // BULLET CASE:
1257 // First, we indent 10 spaces, and then place the bullet
1258 // character (in this case, an asterisk), then we pad it
1259 // again with 10 spaces
1260 // Note: this works only for additional lines of a
1261 // bulleted list, so that is why there is a bullet format
1262 // in the beginning of format_text()
1263 if (_bullet)
1265 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1266 SWF::TextRecord::GlyphEntry ge;
1267 ge.index = space;
1269 const float scale = getFontHeight() /
1270 static_cast<float>(_font->unitsPerEM(_embedFonts));
1272 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1274 rec.addGlyph(ge,5);
1275 _glyphcount += 5;
1277 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1278 ge.index = bullet;
1279 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1280 rec.addGlyph(ge);
1281 ++_glyphcount;
1283 ge.index = space;
1284 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1286 rec.addGlyph(ge,4);
1287 _glyphcount += 4;
1291 void
1292 TextField::handleChar(std::wstring::const_iterator& it,
1293 const std::wstring::const_iterator& e, boost::int32_t& x,
1294 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1295 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1297 LineStarts::iterator linestartit = _line_starts.begin();
1298 LineStarts::const_iterator linestartend = _line_starts.end();
1300 float scale = _fontHeight /
1301 static_cast<float>(_font->unitsPerEM(_embedFonts));
1302 float fontDescent = _font->descent(_embedFonts) * scale;
1304 // TODO: work out how leading should be implemented.
1305 const float leading = 0;
1306 const float fontLeading = 0;
1308 boost::uint32_t code = 0;
1309 while (it != e)
1311 code = *it++;
1312 if (!code) break;
1314 if ( _embedFonts )
1316 x += rec.getFont()->get_kerning_adjustment(last_code,
1317 static_cast<int>(code)) * scale;
1318 last_code = static_cast<int>(code);
1321 // Expand the bounding-box to the lower-right corner of each glyph as
1322 // we generate it.
1323 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1324 switch (code)
1326 case 27:
1327 // Ignore escape
1328 break;
1329 case 9:
1330 insertTab(rec, x, scale);
1331 break;
1332 case 8:
1333 // Backspace
1335 // This is a limited hack to enable overstrike effects.
1336 // It backs the cursor up by one DisplayObject and then continues
1337 // the layout. E.g. you can use this to display an underline
1338 // cursor inside a simulated text-entry box.
1340 // ActionScript understands the '\b' escape sequence
1341 // for inserting a BS DisplayObject.
1343 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1344 // OVER NEWLINES, ETC.
1346 if (!rec.glyphs().empty())
1348 // Peek at the previous glyph, and zero out its advance
1349 // value, so the next char overwrites it.
1350 float advance = rec.glyphs().back().advance;
1351 x -= advance;
1352 // Remove one glyph
1353 rec.clearGlyphs(1);
1355 continue;
1356 case 13:
1357 case 10:
1359 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1360 break;
1362 case '<':
1363 if (doHtml())
1365 //close out this stretch of glyphs
1366 _textRecords.push_back(rec);
1367 rec.clearGlyphs();
1368 _recordStarts.push_back(_glyphcount);
1369 if (*it == '/') {
1370 while (it != e && *it != '>') {
1371 ++it;
1373 ++it;
1374 return;
1376 LOG_ONCE(log_debug("HTML in a text field is unsupported, "
1377 "gnash will just ignore the tags and "
1378 "print their content"));
1380 std::wstring discard;
1381 std::map<std::string,std::string> attributes;
1382 SWF::TextRecord newrec;
1383 newrec.setFont(rec.getFont());
1384 newrec.setUnderline(rec.underline());
1385 newrec.setColor(rec.color());
1386 newrec.setTextHeight(rec.textHeight());
1387 newrec.setXOffset(x);
1388 newrec.setYOffset(y);
1389 bool selfclosing = false;
1390 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1391 std::string s(discard.begin(), discard.end());
1393 std::map<std::string,std::string>::const_iterator attloc;
1395 if (!complete) {
1396 //parsing went wrong
1397 continue;
1398 } else {
1399 // Don't think this is the best way to match with
1400 // tags...
1401 // TODO: assumes tags are properly nested. This isn't
1402 // correct.
1403 if (s == "U") {
1404 //underline
1405 newrec.setUnderline(true);
1406 handleChar(it, e, x, y, newrec, last_code,
1407 last_space_glyph, last_line_start_record);
1409 else if (s == "A") {
1410 // anchor (blue text).
1411 rgba color(0, 0, 0xff, 0xff);
1412 newrec.setColor(color);
1413 newrec.setUnderline(true);
1414 attloc = attributes.find("HREF");
1415 if (attloc != attributes.end()) {
1416 newrec.setURL(attloc->second);
1418 attloc = attributes.find("TARGET");
1419 if (attloc !=attributes.end()) {
1420 newrec.setTarget(attloc->second);
1422 handleChar(it, e, x, y, newrec, last_code,
1423 last_space_glyph, last_line_start_record);
1425 else if (s == "B") {
1426 //bold
1427 Font* boldfont =
1428 fontlib::get_font(rec.getFont()->name(),
1429 true, rec.getFont()->isItalic());
1430 newrec.setFont(boldfont);
1431 handleChar(it, e, x, y, newrec, last_code,
1432 last_space_glyph, last_line_start_record);
1434 else if (s == "FONT") {
1435 //font
1436 boost::uint16_t originalsize = _fontHeight;
1437 attloc = attributes.find("COLOR");
1438 if (attloc != attributes.end()) {
1439 std::string hexval(attloc->second);
1440 if (hexval.empty() || hexval[0] != '#') {
1441 // FIXME: should this be a log_aserror
1442 // or log_unimpl ? It is triggered
1443 // by TextFieldHTML.as
1444 log_error(_("Unexpected value '%s' in TextField font color attribute"),
1445 hexval);
1447 else {
1448 hexval.erase(0, 1);
1449 // font COLOR attribute
1450 const rgba color =
1451 colorFromHexString(hexval);
1452 newrec.setColor(color);
1455 attloc = attributes.find("FACE");
1456 if (attloc != attributes.end()) {
1457 if (attloc->second.empty()) {
1458 IF_VERBOSE_ASCODING_ERRORS(
1459 log_aserror(_("Expected a font name in FACE attribute."))
1461 } else {
1462 //font FACE attribute
1463 Font* newfont =
1464 fontlib::get_font(attloc->second,
1465 rec.getFont()->isBold(),
1466 rec.getFont()->isItalic());
1467 newrec.setFont(newfont);
1470 attloc = attributes.find("SIZE");
1471 if (attloc != attributes.end()) {
1472 //font SIZE attribute
1473 std::string firstchar = attloc->second.substr(0,1);
1474 if (firstchar == "+") {
1475 newrec.setTextHeight(rec.textHeight() +
1477 (pixelsToTwips(std::strtol(
1478 attloc->second.substr(1,attloc->second.length()-1).data(),
1479 NULL,10))));
1480 newrec.setYOffset(PADDING_TWIPS +
1481 newrec.textHeight() +
1482 (fontLeading - fontDescent));
1483 _fontHeight += pixelsToTwips(std::strtol(
1484 attloc->second.substr(1,attloc->second.length()-1).data(),
1485 NULL,10));
1486 } else if (firstchar == "-") {
1487 newrec.setTextHeight(rec.textHeight() -
1488 (pixelsToTwips(std::strtol(
1489 attloc->second.substr(1,attloc->second.length()-1).data(),
1490 NULL,10))));
1491 newrec.setYOffset(PADDING_TWIPS +
1492 newrec.textHeight() +
1493 (fontLeading - fontDescent));
1494 _fontHeight -= pixelsToTwips(std::strtol(
1495 attloc->second.substr(1,attloc->second.length()-1).data(),
1496 NULL,10));
1497 } else {
1498 newrec.setTextHeight(pixelsToTwips(std::strtol(
1499 attloc->second.data(), NULL, 10)));
1500 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1501 (fontLeading - fontDescent));
1502 _fontHeight = pixelsToTwips(std::strtol(
1503 attloc->second.data(), NULL, 10));
1506 handleChar(it, e, x, y, newrec, last_code,
1507 last_space_glyph, last_line_start_record);
1508 _fontHeight = originalsize;
1509 y = newrec.yOffset();
1511 else if (s == "IMG") {
1512 //image
1513 log_unimpl(_("<img> HTML tag in TextField"));
1514 handleChar(it, e, x, y, newrec, last_code,
1515 last_space_glyph, last_line_start_record);
1517 else if (s == "I") {
1518 //italic
1519 Font* italicfont =
1520 fontlib::get_font(rec.getFont()->name(),
1521 rec.getFont()->isBold(), true);
1522 newrec.setFont(italicfont);
1523 handleChar(it, e, x, y, newrec, last_code,
1524 last_space_glyph, last_line_start_record);
1525 } else if (s == "LI") {
1526 //list item (bullet)
1527 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1528 SWF::TextRecord::GlyphEntry ge;
1529 ge.index = space;
1530 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1531 newrec.addGlyph(ge, 5);
1533 // We use an asterisk instead of a bullet
1534 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1535 ge.index = bullet;
1536 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1537 newrec.addGlyph(ge);
1539 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1540 ge.index = space;
1541 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1542 newrec.addGlyph(ge, 4);
1544 handleChar(it, e, x, y, newrec, last_code,
1545 last_space_glyph, last_line_start_record);
1546 newLine(x, y, newrec, last_space_glyph,
1547 last_line_start_record, 1.0);
1549 else if (s == "SPAN") {
1550 //span
1551 log_unimpl(_("<span> HTML tag in TextField"));
1552 handleChar(it, e, x, y, newrec, last_code,
1553 last_space_glyph, last_line_start_record);
1555 else if (s == "TEXTFORMAT") {
1556 log_debug("in textformat");
1557 //textformat
1558 boost::uint16_t originalblockindent = getBlockIndent();
1559 boost::uint16_t originalindent = getIndent();
1560 boost::uint16_t originalleading = getLeading();
1561 boost::uint16_t originalleftmargin = getLeftMargin();
1562 boost::uint16_t originalrightmargin = getRightMargin();
1563 std::vector<int> originaltabstops = getTabStops();
1564 attloc = attributes.find("BLOCKINDENT");
1565 if (attloc != attributes.end()) {
1566 //textformat BLOCKINDENT attribute
1567 setBlockIndent(pixelsToTwips(std::strtol(
1568 attloc->second.data(), NULL, 10)));
1569 if (newrec.xOffset() == std::max(0, originalleftmargin +
1570 originalindent + originalblockindent) + PADDING_TWIPS) {
1571 //if beginning of line, indent
1572 x = std::max(0, getLeftMargin() +
1573 getIndent() + getBlockIndent())
1574 + PADDING_TWIPS;
1575 newrec.setXOffset(x);
1578 attloc = attributes.find("INDENT");
1579 if (attloc != attributes.end()) {
1580 //textformat INDENT attribute
1581 setIndent(pixelsToTwips(std::strtol(
1582 attloc->second.data(), NULL, 10)));
1583 if (newrec.xOffset() == std::max(0, originalleftmargin +
1584 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1585 //if beginning of line, indent
1586 x = std::max(0, getLeftMargin() +
1587 getIndent() + getBlockIndent())
1588 + PADDING_TWIPS;
1589 newrec.setXOffset(x);
1592 attloc = attributes.find("LEADING");
1593 if (attloc != attributes.end()) {
1594 //textformat LEADING attribute
1595 setLeading(pixelsToTwips(std::strtol(
1596 attloc->second.data(), NULL, 10)));
1598 attloc = attributes.find("LEFTMARGIN");
1599 if (attloc != attributes.end()) {
1600 //textformat LEFTMARGIN attribute
1601 setLeftMargin(pixelsToTwips(std::strtol(
1602 attloc->second.data(), NULL, 10)));
1603 if (newrec.xOffset() == std::max(0, originalleftmargin +
1604 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1605 //if beginning of line, indent
1606 x = std::max(0, getLeftMargin() +
1607 getIndent() + getBlockIndent())
1608 + PADDING_TWIPS;
1609 newrec.setXOffset(x);
1612 attloc = attributes.find("RIGHTMARGIN");
1613 if (attloc != attributes.end()) {
1614 //textformat RIGHTMARGIN attribute
1615 setRightMargin(pixelsToTwips(std::strtol(
1616 attloc->second.data(), NULL, 10)));
1617 //FIXME:Should not apply this to this line if we are not at
1618 //beginning of line. Not sure how to do that.
1620 attloc = attributes.find("TABSTOPS");
1621 if (attloc != attributes.end()) {
1622 //textformat TABSTOPS attribute
1623 log_unimpl(_("HTML <textformat> tag tabstops attribute"));
1625 handleChar(it, e, x, y, newrec, last_code,
1626 last_space_glyph, last_line_start_record);
1627 setBlockIndent(originalblockindent);
1628 setIndent(originalindent);
1629 setLeading(originalleading);
1630 setLeftMargin(originalleftmargin);
1631 setRightMargin(originalrightmargin);
1632 setTabStops(originaltabstops);
1634 else if (s == "P") {
1635 //paragraph
1636 if (_display == TEXTFORMAT_BLOCK) {
1637 handleChar(it, e, x, y, newrec, last_code,
1638 last_space_glyph,
1639 last_line_start_record);
1640 newLine(x, y, rec, last_space_glyph,
1641 last_line_start_record, 1.0);
1642 newLine(x, y, rec, last_space_glyph,
1643 last_line_start_record, 1.5);
1645 else {
1646 handleChar(it, e, x, y, newrec, last_code,
1647 last_space_glyph,
1648 last_line_start_record);
1651 else if (s == "BR" || s == "SBR") {
1652 //line break
1653 newLine(x, y, rec, last_space_glyph,
1654 last_line_start_record, 1.0);
1656 else {
1657 log_debug("<%s> tag is unsupported", s);
1658 if (!selfclosing) { //then recurse, look for closing tag
1659 handleChar(it, e, x, y, newrec, last_code,
1660 last_space_glyph, last_line_start_record);
1664 rec.setXOffset(x);
1665 rec.setYOffset(y);
1666 continue;
1668 // If HTML isn't enabled, carry on and insert the glyph.
1669 // FIXME: do we also want to be changing last_space_glyph?
1670 // ...because we are...
1671 case 32:
1672 last_space_glyph = rec.glyphs().size();
1673 // Don't break, as we still need to insert the space glyph.
1675 default:
1677 if ( password() )
1679 SWF::TextRecord::GlyphEntry ge;
1680 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1681 ge.index = bullet;
1682 ge.advance = scale * rec.getFont()->get_advance(bullet,
1683 _embedFonts);
1684 rec.addGlyph(ge);
1685 ++_glyphcount;
1686 break;
1688 // The font table holds up to 65535 glyphs. Casting
1689 // from uint32_t would, in the event that the code
1690 // is higher than 65535, result in the wrong DisplayObject
1691 // being chosen. Flash can currently only handle 16-bit
1692 // values.
1693 int index = rec.getFont()->get_glyph_index(
1694 static_cast<boost::uint16_t>(code), _embedFonts);
1696 IF_VERBOSE_MALFORMED_SWF (
1697 if (index == -1)
1699 // Missing glyph! Log the first few errors.
1700 static int s_log_count = 0;
1701 if (s_log_count < 10)
1703 s_log_count++;
1704 if (_embedFonts)
1706 log_swferror(_("TextField: missing embedded "
1707 "glyph for char %d. Make sure DisplayObject "
1708 "shapes for font %s are being exported "
1709 "into your SWF file"),
1710 code, _font->name());
1712 else
1714 log_swferror(_("TextField: missing device "
1715 "glyph for char %d. Maybe you don't have "
1716 "font '%s' installed in your system."),
1717 code, _font->name());
1721 // Drop through and use index == -1; this will display
1722 // using the empty-box glyph
1726 SWF::TextRecord::GlyphEntry ge;
1727 ge.index = index;
1728 ge.advance = scale * rec.getFont()->get_advance(index,
1729 _embedFonts);
1731 rec.addGlyph(ge);
1733 x += ge.advance;
1734 ++_glyphcount;
1738 float width = _bounds.width();
1739 if (x >= width - getRightMargin() - PADDING_TWIPS)
1741 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1742 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1743 getTarget(), _bounds);
1744 #endif
1746 // No wrap and no resize: truncate
1747 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1749 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1750 log_debug(" wordWrap=false, autoSize=none");
1751 #endif
1752 // Truncate long line, but keep expanding text box
1753 bool newlinefound = false;
1754 while (it != e)
1756 code = *it++;
1757 if (_embedFonts)
1759 x += rec.getFont()->get_kerning_adjustment(last_code,
1760 static_cast<int>(code)) * scale;
1761 last_code = code;
1763 // Expand the bounding-box to the lower-right corner
1764 // of each glyph, even if we don't display it
1765 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1766 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1767 log_debug("Text box expanded to %s (width: %f)",
1768 m_text_bounding_box, m_text_bounding_box.width());
1769 #endif
1771 if (code == 13 || code == 10)
1773 newlinefound = true;
1774 break;
1777 int index = rec.getFont()->get_glyph_index(
1778 static_cast<boost::uint16_t>(code), _embedFonts);
1779 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1782 if (!newlinefound) break;
1784 else if (doWordWrap()) {
1786 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1787 log_debug(" wordWrap=true");
1788 #endif
1790 // Insert newline if there's space or autosize != none
1792 // Close out this stretch of glyphs.
1793 _textRecords.push_back(rec);
1795 float previous_x = x;
1796 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1797 y += _fontHeight + leading;
1798 if (y >= _bounds.height()) {
1799 ++_maxScroll;
1802 // Start a new record on the next line.
1803 rec.clearGlyphs();
1804 rec.setXOffset(x);
1805 rec.setYOffset(y);
1807 // TODO : what if m_text_glyph_records is empty ?
1808 // Is it possible ?
1809 assert(!_textRecords.empty());
1810 SWF::TextRecord& last_line = _textRecords.back();
1812 linestartit = _line_starts.begin();
1813 linestartend = _line_starts.end();
1814 if (last_space_glyph == -1)
1816 // Pull the previous glyph down onto the
1817 // new line.
1818 if (!last_line.glyphs().empty())
1820 rec.addGlyph(last_line.glyphs().back());
1821 x += last_line.glyphs().back().advance;
1822 previous_x -= last_line.glyphs().back().advance;
1823 last_line.clearGlyphs(1);
1824 //record the new line start
1826 const size_t currentPos = _glyphcount;
1827 while (linestartit != linestartend &&
1828 *linestartit + 1 <= currentPos)
1830 ++linestartit;
1832 _line_starts.insert(linestartit, currentPos);
1833 _recordStarts.push_back(currentPos);
1835 } else {
1836 // Move the previous word down onto the next line.
1838 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1840 const SWF::TextRecord::Glyphs::size_type lineSize =
1841 last_line.glyphs().size();
1842 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1843 ++i)
1845 rec.addGlyph(last_line.glyphs()[i]);
1846 x += last_line.glyphs()[i].advance;
1847 previous_x -= last_line.glyphs()[i].advance;
1849 last_line.clearGlyphs(lineSize - last_space_glyph);
1851 // record the position at the start of this line as
1852 // a line_start
1853 const size_t linestartpos = _glyphcount -
1854 rec.glyphs().size();
1856 while (linestartit < linestartend &&
1857 *linestartit < linestartpos)
1859 ++linestartit;
1861 _line_starts.insert(linestartit, linestartpos);
1862 _recordStarts.push_back(linestartpos);
1865 align_line(getTextAlignment(), last_line_start_record, previous_x);
1867 last_space_glyph = -1;
1868 last_line_start_record = _textRecords.size();
1871 else
1873 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1874 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1875 #endif
1882 TextField::getDefinitionVersion() const
1884 // TODO: work out if this correct.
1885 return get_root()->getDefinitionVersion();
1889 TextField::VariableRef
1890 TextField::parseTextVariableRef(const std::string& variableName) const
1892 VariableRef ret;
1893 ret.first = 0;
1895 #ifdef DEBUG_DYNTEXT_VARIABLES
1896 log_debug("VariableName: %s", variableName);
1897 #endif
1899 /// Why isn't get_environment const again ?
1900 const as_environment& env = const_cast<TextField*>(this)->get_environment();
1902 as_object* target = getObject(env.target());
1903 if (!target) {
1904 IF_VERBOSE_MALFORMED_SWF(
1905 log_swferror(_("Current environment has no target, "
1906 "can't bind VariableName (%s) associated to "
1907 "text field. Gnash will try to register "
1908 "again on next access."), variableName);
1910 return ret;
1913 // If the variable string contains a path, we extract
1914 // the appropriate target from it and update the variable
1915 // name. We copy the string so we can assign to it if necessary.
1916 std::string parsedName = variableName;
1917 std::string path, var;
1918 if (parsePath(variableName, path, var)) {
1919 #ifdef DEBUG_DYNTEXT_VARIABLES
1920 log_debug("Variable text Path: %s, Var: %s", path, var);
1921 #endif
1922 // find target for the path component
1923 // we use our parent's environment for this
1924 target = findObject(env, path);
1926 parsedName = var;
1929 if (!target) {
1930 IF_VERBOSE_MALFORMED_SWF(
1931 log_swferror(_("VariableName associated to text field refers "
1932 "to an unknown target (%s). It is possible that the "
1933 "DisplayObject will be instantiated later in the SWF "
1934 "stream. Gnash will try to register again on next "
1935 "access."), path);
1937 return ret;
1940 ret.first = target;
1941 ret.second = getURI(getVM(*object()), parsedName);
1943 return ret;
1946 void
1947 TextField::registerTextVariable()
1949 //#define DEBUG_DYNTEXT_VARIABLES 1
1951 #ifdef DEBUG_DYNTEXT_VARIABLES
1952 log_debug("registerTextVariable() called");
1953 #endif
1955 if (_text_variable_registered) {
1956 return;
1959 if (_variable_name.empty()) {
1960 _text_variable_registered = true;
1961 return;
1964 VariableRef varRef = parseTextVariableRef(_variable_name);
1965 as_object* target = varRef.first;
1966 if (!target) {
1967 log_debug("VariableName associated to text field (%s) refer to "
1968 "an unknown target. It is possible that the DisplayObject "
1969 "will be instantiated later in the SWF stream. "
1970 "Gnash will try to register again on next access.",
1971 _variable_name);
1972 return;
1975 const ObjectURI& key = varRef.second;
1976 as_object* obj = getObject(this);
1977 const int version = getSWFVersion(*obj);
1979 // check if the VariableName already has a value,
1980 // in that case update text value
1981 as_value val;
1982 if (target->get_member(key, &val)) {
1983 // TODO: pass environment to to_string ?
1984 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
1986 else if (_textDefined) {
1987 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
1988 target->set_member(key, newVal);
1991 MovieClip* sprite = get<MovieClip>(target);
1993 if (sprite) {
1994 // add the textfield variable to the target sprite
1995 // TODO: have set_textfield_variable take a string_table::key instead ?
1996 sprite->set_textfield_variable(key, this);
1999 _text_variable_registered = true;
2002 /// Parses an HTML tag (between < and >) and puts
2003 /// the contents into tag. Returns false if the
2004 /// tag was incomplete. The iterator is moved to after
2005 /// the closing tag or the end of the string.
2006 bool
2007 TextField::parseHTML(std::wstring& tag,
2008 std::map<std::string, std::string>& attributes,
2009 std::wstring::const_iterator& it,
2010 const std::wstring::const_iterator& e,
2011 bool& selfclosing) const
2013 while (it != e && *it != ' ') {
2014 if (*it == '/') {
2015 ++it;
2016 if (*it == '>') {
2017 ++it;
2018 selfclosing = true;
2019 return true;
2020 } else {
2021 while (it != e) {
2022 ++it;
2024 log_error(_("invalid HTML tag"));
2025 return false;
2028 if (*it == '>') {
2029 ++it;
2030 return true;
2033 // Check for NULL character
2034 if (*it == 0) {
2035 log_error(_("found NULL character in htmlText"));
2036 return false;
2038 tag.push_back(std::toupper(*it));
2039 ++it;
2041 while (it != e && *it == ' ') {
2042 ++it; //skip over spaces
2044 if (*it == '>') {
2045 ++it;
2046 return true;
2048 if (*it == '/') {
2049 ++it;
2050 if (*it == '>') {
2051 ++it;
2052 selfclosing = true;
2053 return true;
2054 } else {
2055 while (it != e) {
2056 ++it;
2058 log_error(_("invalid HTML tag"));
2059 return false;
2063 std::string attname;
2064 std::string attvalue;
2066 //attributes
2067 while (it != e && *it != '>') {
2068 while (it != e && *it != '=' && *it != ' ') {
2070 if (*it == 0) {
2071 log_error(_("found NULL character in htmlText"));
2072 return false;
2074 if (*it == '>') {
2075 log_error(_("malformed HTML tag, invalid attribute name"));
2076 while (it != e) {
2077 ++it;
2079 return false;
2082 attname.push_back(std::toupper(*it));
2083 ++it;
2085 while (it != e && (*it == ' ' || *it == '=')) {
2086 ++it; //skip over spaces and '='
2089 if (it == e) return false;
2090 const char q = *it;
2091 if (q != '"' && q != '\'') {
2092 // This is not an attribute.
2093 while (it != e) ++it;
2094 return false;
2097 // Advance past attribute opener
2098 ++it;
2099 while (it != e && *it != q) {
2101 if (*it == 0) {
2102 log_error(_("found NULL character in htmlText"));
2103 return false;
2106 attvalue.push_back(std::toupper(*it));
2107 ++it;
2110 if (it == e) return false;
2112 if (*it != q) {
2113 while (it != e) ++it;
2114 return false;
2117 // Skip attribute closer.
2118 ++it;
2120 attributes.insert(std::make_pair(attname, attvalue));
2121 attname.clear();
2122 attvalue.clear();
2124 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2125 log_error(_("malformed HTML tag, invalid attribute value"));
2126 while (it != e) {
2127 ++it;
2129 return false;
2131 if (*it == ' ') {
2132 while (it != e && *it == ' ') {
2133 ++it; //skip over spaces
2136 if (*it == '>') {
2137 ++it;
2138 return true;
2139 } else if (*it == '/') {
2140 ++it;
2141 if (*it == '>') {
2142 ++it;
2143 selfclosing = true;
2144 return true;
2145 } else {
2146 while (it != e) {
2147 ++it;
2149 log_error(_("invalid HTML tag"));
2150 return false;
2155 #ifdef GNASH_DEBUG_TEXTFIELDS
2156 log_debug("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2157 #endif
2158 log_error(_("I declare this a HTML syntax error"));
2159 return false; //since we did not return already, must be malformed...?
2162 void
2163 TextField::set_variable_name(const std::string& newname)
2165 if (newname != _variable_name) {
2166 _variable_name = newname;
2168 // The name was empty or undefined, so there's nothing more to do.
2169 if (_variable_name.empty()) return;
2171 _text_variable_registered = false;
2173 #ifdef DEBUG_DYNTEXT_VARIABLES
2174 log_debug("Calling updateText after change of variable name");
2175 #endif
2177 // Use the original definition text if this isn't dynamically
2178 // created.
2179 if (_tag) updateText(_tag->defaultText());
2181 #ifdef DEBUG_DYNTEXT_VARIABLES
2182 log_debug("Calling registerTextVariable after change of variable "
2183 "name and updateText call");
2184 #endif
2185 registerTextVariable();
2189 bool
2190 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2192 const SWFMatrix wm = getWorldMatrix(*this).invert();
2193 point lp(x, y);
2194 wm.transform(lp);
2195 return _bounds.point_test(lp.x, lp.y);
2198 bool
2199 TextField::getDrawBorder() const
2201 return _drawBorder;
2204 void
2205 TextField::setDrawBorder(bool val)
2207 if (_drawBorder != val) {
2208 set_invalidated();
2209 _drawBorder = val;
2213 rgba
2214 TextField::getBorderColor() const
2216 return _borderColor;
2219 void
2220 TextField::setBorderColor(const rgba& col)
2222 if (_borderColor != col) {
2223 set_invalidated();
2224 _borderColor = col;
2228 bool
2229 TextField::getDrawBackground() const
2231 return _drawBackground;
2234 void
2235 TextField::setDrawBackground(bool val)
2237 if (_drawBackground != val) {
2238 set_invalidated();
2239 _drawBackground = val;
2243 rgba
2244 TextField::getBackgroundColor() const
2246 return _backgroundColor;
2249 void
2250 TextField::setBackgroundColor(const rgba& col)
2252 if (_backgroundColor != col) {
2253 set_invalidated();
2254 _backgroundColor = col;
2258 void
2259 TextField::setTextColor(const rgba& col)
2261 if (_textColor != col) {
2263 set_invalidated();
2264 _textColor = col;
2265 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2266 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2270 void
2271 TextField::setEmbedFonts(bool use)
2273 if (_embedFonts != use) {
2274 set_invalidated();
2275 _embedFonts=use;
2276 format_text();
2280 void
2281 TextField::setWordWrap(bool wrap)
2283 if (_wordWrap != wrap) {
2284 set_invalidated();
2285 _wordWrap = wrap;
2286 format_text();
2290 void
2291 TextField::setLeading(boost::int16_t h)
2293 if (_leading != h) {
2294 set_invalidated();
2295 _leading = h;
2299 void
2300 TextField::setUnderlined(bool v)
2302 if (_underlined != v) {
2303 set_invalidated();
2304 _underlined = v;
2308 void
2309 TextField::setBullet(bool b)
2311 if (_bullet != b) {
2312 _bullet = b;
2316 void
2317 TextField::setTabStops(const std::vector<int>& tabStops)
2319 _tabStops.resize(tabStops.size());
2321 for (size_t i = 0; i < tabStops.size(); i ++) {
2322 _tabStops[i] = pixelsToTwips(tabStops[i]);
2325 set_invalidated();
2328 void
2329 TextField::setURL(std::string url)
2331 if (_url != url) {
2332 set_invalidated();
2333 _url = url;
2337 void
2338 TextField::setTarget(std::string target)
2340 if (_target != target) {
2341 set_invalidated();
2342 _target = target;
2346 void
2347 TextField::setDisplay(TextFormatDisplay display)
2349 if (_display != display) {
2350 set_invalidated();
2351 _display = display;
2355 void
2356 TextField::setAlignment(TextAlignment h)
2358 if (_alignment != h) {
2359 set_invalidated();
2360 _alignment = h;
2364 void
2365 TextField::setIndent(boost::uint16_t h)
2367 if (_indent != h) {
2368 set_invalidated();
2369 _indent = h;
2373 void
2374 TextField::setBlockIndent(boost::uint16_t h)
2376 if (_blockIndent != h) {
2377 set_invalidated();
2378 _blockIndent = h;
2382 void
2383 TextField::setRightMargin(boost::uint16_t h)
2385 if (_rightMargin != h) {
2386 set_invalidated();
2387 _rightMargin = h;
2391 void
2392 TextField::setLeftMargin(boost::uint16_t h)
2394 if (_leftMargin != h) {
2395 set_invalidated();
2396 _leftMargin = h;
2400 void
2401 TextField::setFontHeight(boost::uint16_t h)
2403 if (_fontHeight != h) {
2404 set_invalidated();
2405 _fontHeight = h;
2410 TextField::TypeValue
2411 TextField::parseTypeValue(const std::string& val)
2413 StringNoCaseEqual cmp;
2415 if (cmp(val, "input")) return typeInput;
2416 if (cmp(val, "dynamic")) return typeDynamic;
2417 return typeInvalid;
2421 const char*
2422 TextField::typeValueName(TypeValue val)
2424 switch (val) {
2425 case typeInput:
2426 //log_debug("typeInput returned as 'input'");
2427 return "input";
2428 case typeDynamic:
2429 //log_debug("typeDynamic returned as 'dynamic'");
2430 return "dynamic";
2431 default:
2432 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2433 return "invalid";
2438 void
2439 TextField::setAutoSize(AutoSize val)
2441 if (val == _autoSize) return;
2443 set_invalidated();
2444 _autoSize = val;
2445 format_text();
2448 TextField::TextAlignment
2449 TextField::getTextAlignment()
2451 TextAlignment textAlignment = getAlignment();
2453 switch (_autoSize) {
2454 case AUTOSIZE_CENTER:
2455 textAlignment = ALIGN_CENTER;
2456 break;
2457 case AUTOSIZE_LEFT:
2458 textAlignment = ALIGN_LEFT;
2459 break;
2460 case AUTOSIZE_RIGHT:
2461 textAlignment = ALIGN_RIGHT;
2462 break;
2463 default:
2464 // Leave it as it was.
2465 break;
2468 return textAlignment;
2471 void
2472 TextField::onChanged()
2474 as_object* obj = getObject(this);
2475 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2478 /// This is called by movie_root when focus is applied to this TextField.
2480 /// The return value is true if the TextField can receive focus.
2481 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2482 /// handle focus.
2483 bool
2484 TextField::handleFocus()
2486 set_invalidated();
2488 /// Select the entire text on focus.
2489 setSelection(0, _text.length());
2491 m_has_focus = true;
2493 m_cursor = _text.size();
2494 format_text();
2495 return true;
2498 /// This is called by movie_root when focus is removed from the
2499 /// current TextField.
2500 void
2501 TextField::killFocus()
2503 if (!m_has_focus) return;
2504 set_invalidated();
2505 m_has_focus = false;
2506 format_text(); // is this needed ?
2509 void
2510 TextField::setWidth(double newwidth)
2512 const SWFRect& bounds = getBounds();
2513 _bounds.set_to_rect(bounds.get_x_min(),
2514 bounds.get_y_min(),
2515 bounds.get_x_min() + newwidth,
2516 bounds.get_y_max());
2519 void
2520 TextField::setHeight(double newheight)
2522 const SWFRect& bounds = getBounds();
2523 _bounds.set_to_rect(bounds.get_x_min(),
2524 bounds.get_y_min(),
2525 bounds.get_x_max(),
2526 bounds.get_y_min() + newheight);
2529 } // namespace gnash
2532 // Local Variables:
2533 // mode: C++
2534 // indent-tabs-mode: t
2535 // End: