Drop extra semicolon
[gnash.git] / libcore / TextField.cpp
blobc4cea0d1f92e336a82f235bd041b1a980783c4f9
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> 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 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 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 float scale = getFontHeight() /
348 static_cast<float>(_font->unitsPerEM(_embedFonts));
349 float fontLeading = _font->leading() * scale;
351 //offset the lines
352 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
353 size_t recordline;
354 for (size_t i = 0; i < _textRecords.size(); ++i) {
355 recordline = 0;
356 //find the line the record is on
357 while (recordline < _line_starts.size() &&
358 _line_starts[recordline] <= _recordStarts[i]) {
359 ++recordline;
361 //offset the line
362 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
363 //add the lines we want to the display record
364 if (_textRecords[i].yOffset() > 0 &&
365 _textRecords[i].yOffset() < _bounds.height()) {
366 _displayRecords.push_back(_textRecords[i]);
370 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
371 _embedFonts);
373 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
375 clear_invalidated();
379 void
380 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
382 if (!force && !invalidated()) return; // no need to redraw
384 ranges.add(m_old_invalidated_ranges);
386 const SWFMatrix& wm = getWorldMatrix(*this);
388 SWFRect bounds = getBounds();
389 bounds.expand_to_rect(m_text_bounding_box);
390 wm.transform(bounds);
391 ranges.add(bounds.getRange());
394 void
395 TextField::setRestrict(const std::string& restrict)
397 _restrictDefined = true;
399 std::string::const_iterator rit = restrict.begin();
400 std::string::const_iterator re = restrict.end();
401 std::set<wchar_t>::const_iterator locate;
403 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
404 for (unsigned int i = 0; i <= 255; ++i) {
405 _restrictedchars.insert(char(i));
407 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
408 _restrictedchars.clear();
411 while (rit != re) {
412 while (rit != re && *rit != '^') { //This loop allows chars
413 if (*rit == '-') {
414 log_error("invalid restrict string");
415 return;
416 } else if (*(rit+1) == '-') {
417 if (re - (rit+2) != 0) {
418 unsigned int q = *(rit+2);
419 for (unsigned int p = *rit; p <= q; (++p)){
420 _restrictedchars.insert(char(p));
422 rit += 3;
423 } else {
424 log_error("invalid restrict string");
425 return;
427 } else if (*rit == '\\') {
428 ++rit;
429 _restrictedchars.insert(*rit);
430 ++rit;
431 } else {
432 _restrictedchars.insert(*rit);
433 ++rit;
436 if (rit != re) {
437 ++rit;
439 while (rit != re && *rit != '^') { //This loop restricts chars
440 locate = _restrictedchars.find(*rit);
441 if (*rit == '-') {
442 log_error("invalid restrict string");
443 return;
444 } else if (*(rit+1) == '-') {
445 if (re - (rit+2) != 0) {
446 unsigned int q = *(rit+2);
447 for (unsigned int p = *rit; p <= q; ++p){
448 locate = _restrictedchars.find(p);
449 if(locate != _restrictedchars.end()) {
450 _restrictedchars.erase(locate);
453 ++rit;
454 ++rit;
455 ++rit;
456 } else {
457 log_error("invalid restrict string");
458 return;
460 } else if (*rit == '\\') {
461 ++rit;
462 locate = _restrictedchars.find(*rit);
463 if(locate != _restrictedchars.end()) {
464 _restrictedchars.erase(locate);
466 ++rit;
467 } else {
468 if(locate != _restrictedchars.end()) {
469 _restrictedchars.erase(locate);
471 ++rit;
474 if (rit != re) {
475 ++rit;
478 _restrict = restrict;
481 void
482 TextField::replaceSelection(const std::string& replace)
484 const int version = getSWFVersion(*getObject(this));
485 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
487 assert(_selection.second >= _selection.first);
488 assert(_selection.second <= _text.size());
489 assert(_selection.first <= _text.size());
491 // If the text has changed but the selection hasn't, make sure we
492 // don't access it out of bounds.
493 const size_t start = _selection.first;
494 const size_t end = _selection.second;
496 const size_t replaceLength = wstr.size();
498 _text.replace(start, end - start, wstr);
499 _selection = std::make_pair(start + replaceLength, start + replaceLength);
502 void
503 TextField::setSelection(int start, int end)
505 if (_text.empty()) {
506 _selection = std::make_pair(0, 0);
507 return;
510 const size_t textLength = _text.size();
512 if (start < 0) start = 0;
513 else start = std::min<size_t>(start, textLength);
515 if (end < 0) end = 0;
516 else end = std::min<size_t>(end, textLength);
518 // The cursor position is always set to the end value, even if the
519 // two values are swapped to obtain the selection. Equal values are
520 // fine.
521 m_cursor = end;
522 if (start > end) std::swap(start, end);
524 _selection = std::make_pair(start, end);
527 void
528 TextField::notifyEvent(const event_id& ev)
530 switch (ev.id())
532 case event_id::PRESS:
534 movie_root& root = stage();
535 boost::int32_t x_mouse, y_mouse;
536 boost::tie(x_mouse, y_mouse) = root.mousePosition();
538 SWFMatrix m = getMatrix(*this);
540 x_mouse -= m.get_x_translation();
541 y_mouse -= m.get_y_translation();
543 SWF::TextRecord rec;
545 for (size_t i=0; i < _textRecords.size(); ++i) {
546 if ((x_mouse > _textRecords[i].xOffset()) &&
547 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
548 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
549 (y_mouse < _textRecords[i].yOffset())) {
550 rec = _textRecords[i];
551 break;
555 if (!rec.getURL().empty()) {
556 root.getURL(rec.getURL(), rec.getTarget(), "",
557 MovieClip::METHOD_NONE);
560 break;
562 case event_id::KEY_PRESS:
564 setHtml(false); //editable html fields are not yet implemented
565 std::wstring s = _text;
567 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
568 // The maximum value is about 265, including function keys.
569 // It seems that typing in DisplayObjects outside the Latin-1 set
570 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
571 // is not supported, though a much greater number UTF-8 codes can be
572 // stored and displayed. See utf.h for more information.
573 // This is a limit on the number of key codes, not on the
574 // capacity of strings.
575 gnash::key::code c = ev.keyCode();
578 // maybe _text is changed in ActionScript
579 m_cursor = std::min<size_t>(m_cursor, _text.size());
581 size_t cur_cursor = m_cursor;
582 size_t previouslinesize = 0;
583 size_t nextlinesize = 0;
584 size_t manylines = _line_starts.size();
585 LineStarts::iterator linestartit = _line_starts.begin();
586 LineStarts::const_iterator linestartend = _line_starts.end();
588 switch (c)
590 case key::BACKSPACE:
591 if (isReadOnly()) return;
592 if (m_cursor > 0)
594 s.erase(m_cursor - 1, 1);
595 m_cursor--;
596 setTextValue(s);
598 break;
600 case key::DELETEKEY:
601 if (isReadOnly()) return;
602 if (_glyphcount > m_cursor)
604 s.erase(m_cursor, 1);
605 setTextValue(s);
607 break;
609 case key::INSERT: // TODO
610 if (isReadOnly()) return;
611 break;
613 case key::HOME:
614 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
615 cur_cursor = *linestartit;
616 linestartit++;
618 m_cursor = cur_cursor;
619 break;
621 case key::PGUP:
622 // if going a page up is too far...
623 if(_scroll < _linesindisplay) {
624 _scroll = 0;
625 m_cursor = 0;
626 } else { // go a page up
627 _scroll -= _linesindisplay;
628 m_cursor = _line_starts[_scroll];
630 scrollLines();
631 break;
633 case key::UP:
634 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
635 cur_cursor = *linestartit;
636 linestartit++;
638 //if there is no previous line
639 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
640 m_cursor = 0;
641 break;
643 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
644 //if the previous line is smaller
645 if (m_cursor - cur_cursor > previouslinesize) {
646 m_cursor = *(--(--linestartit)) + previouslinesize;
647 } else {
648 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
650 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
651 --_scroll;
653 scrollLines();
654 break;
656 case key::END:
657 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
658 linestartit++;
660 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
661 break;
663 case key::PGDN:
664 //if going another page down is too far...
665 if(_scroll + _linesindisplay >= manylines) {
666 if(manylines - _linesindisplay <= 0) {
667 _scroll = 0;
668 } else {
669 _scroll = manylines - _linesindisplay;
671 if(m_cursor < _line_starts[_scroll-1]) {
672 m_cursor = _line_starts[_scroll-1];
673 } else {
674 m_cursor = _text.size();
676 } else { //go a page down
677 _scroll += _linesindisplay;
678 m_cursor = _line_starts[_scroll];
680 scrollLines();
681 break;
683 case key::DOWN:
685 while (linestartit < linestartend &&
686 *linestartit <= m_cursor ) {
687 cur_cursor = *linestartit;
688 linestartit++;
691 // linestartit should never be before _line_starts.begin()
692 const size_t currentLine = linestartit -
693 _line_starts.begin();
695 //if there is no next line
696 if (currentLine >= manylines ) {
697 m_cursor = _text.size();
698 break;
700 nextlinesize = _textRecords[currentLine].glyphs().size();
702 //if the next line is smaller
703 if (m_cursor - cur_cursor > nextlinesize) {
704 m_cursor = *linestartit + nextlinesize;
705 } else {
706 //put the cursor at the same character distance
707 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
709 if (_line_starts.size() > _linesindisplay &&
710 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
711 ++_scroll;
713 scrollLines();
714 break;
717 case key::LEFT:
718 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
719 break;
721 case key::RIGHT:
722 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
723 _glyphcount;
724 break;
726 case key::ENTER:
727 if (isReadOnly()) return;
728 if (!multiline()) break;
730 default:
732 if (maxChars() != 0) {
733 if (_maxChars <= _glyphcount) {
734 break;
738 if (isReadOnly()) return;
739 wchar_t t = static_cast<wchar_t>(
740 gnash::key::codeMap[c][key::ASCII]);
741 if (t != 0) {
743 if (!_restrictDefined) {
744 // Insert one copy of the character
745 // at the cursor position.
746 s.insert(m_cursor, 1, t);
747 m_cursor++;
748 } else if (_restrictedchars.count(t)) {
749 // Insert one copy of the character
750 // at the cursor position.
751 s.insert(m_cursor, 1, t);
752 m_cursor++;
753 } else if (_restrictedchars.count(tolower(t))) {
754 // restrict substitutes the opposite case
755 s.insert(m_cursor, 1, tolower(t));
756 m_cursor++;
757 } else if (_restrictedchars.count(toupper(t))) {
758 // restrict substitutes the opposite case
759 s.insert(m_cursor, 1, toupper(t));
760 m_cursor++;
763 setTextValue(s);
765 onChanged();
766 set_invalidated();
769 default:
770 return;
774 InteractiveObject*
775 TextField::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
777 if (!visible()) return 0;
779 // Not selectable, so don't catch mouse events!
780 if (!_selectable) return 0;
782 SWFMatrix m = getMatrix(*this);
783 point p(x, y);
784 m.invert().transform(p);
786 if (_bounds.point_test(p.x, p.y)) return this;
788 return 0;
791 void
792 TextField::updateText(const std::string& str)
794 const int version = getSWFVersion(*getObject(this));
795 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
796 updateText(wstr);
799 void
800 TextField::updateText(const std::wstring& wstr)
802 _textDefined = true;
803 if (_text == wstr) return;
805 set_invalidated();
807 _text = wstr;
809 _selection.first = std::min(_selection.first, _text.size());
810 _selection.second = std::min(_selection.second, _text.size());
812 format_text();
815 void
816 TextField::updateHtmlText(const std::wstring& wstr)
818 if (_htmlText == wstr) return;
820 set_invalidated();
822 _htmlText = wstr;
823 format_text();
826 void
827 TextField::setTextValue(const std::wstring& wstr)
829 updateHtmlText(wstr);
830 updateText(wstr);
832 if (!_variable_name.empty() && _text_variable_registered) {
833 // TODO: notify MovieClip if we have a variable name !
834 VariableRef ref = parseTextVariableRef(_variable_name);
835 as_object* tgt = ref.first;
836 if (tgt) {
837 const int version = getSWFVersion(*getObject(this));
838 // we shouldn't truncate, right?
839 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
840 version));
842 else {
843 // nothing to do (too early ?)
844 log_debug("setTextValue: variable name %s points to a non-existent"
845 " target, I guess we would not be registered if this was "
846 "true, or the sprite we've registered our variable name "
847 "has been unloaded", _variable_name);
852 std::string
853 TextField::get_text_value() const
855 // we need the const_cast here because registerTextVariable
856 // *might* change our text value, calling the non-const
857 // setTextValue().
858 // This happens if the TextVariable has not been already registered
859 // and during registration comes out to name an existing variable
860 // with a pre-existing value.
861 const_cast<TextField*>(this)->registerTextVariable();
863 const int version = getSWFVersion(*getObject(this));
865 return utf8::encodeCanonicalString(_text, version);
868 std::string
869 TextField::get_htmltext_value() const
871 const_cast<TextField*>(this)->registerTextVariable();
872 const int version = getSWFVersion(*getObject(this));
873 return utf8::encodeCanonicalString(_htmlText, version);
876 void
877 TextField::setTextFormat(TextFormat_as& tf)
879 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
880 //This is just so we can set individual variables without having to call format_text()
881 //This calls format_text() at the end of setting TextFormat
882 if (tf.align()) setAlignment(*tf.align());
883 if (tf.size()) setFontHeight(*tf.size()); // keep twips
884 if (tf.indent()) setIndent(*tf.indent());
885 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
886 if (tf.leading()) setLeading(*tf.leading());
887 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
888 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
889 if (tf.color()) setTextColor(*tf.color());
890 if (tf.underlined()) setUnderlined(*tf.underlined());
891 if (tf.bullet()) setBullet(*tf.bullet());
892 setDisplay(tf.display());
893 if (tf.tabStops()) setTabStops(*tf.tabStops());
895 // NEED TO IMPLEMENT THESE TWO
896 if (tf.url()) setURL(*tf.url());
897 if (tf.target()) setTarget(*tf.target());
899 format_text();
902 float
903 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
905 float width = _bounds.width();
906 float right_margin = getRightMargin();
908 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
910 if (extra_space <= 0.0f) {
911 #ifdef GNASH_DEBUG_TEXTFIELDS
912 log_debug(_("TextField text doesn't fit in its boundaries: "
913 "width %g, margin %g - nothing to align"),
914 width, right_margin);
915 #endif
916 return 0.0f;
919 float shift_right = 0.0f;
921 switch (align) {
922 case ALIGN_LEFT:
923 // Nothing to do; already aligned left.
924 return 0.0f;
925 case ALIGN_CENTER:
926 // Distribute the space evenly on both sides.
927 shift_right = extra_space / 2;
928 break;
929 case ALIGN_RIGHT:
930 // Shift all the way to the right.
931 shift_right = extra_space;
932 break;
933 case ALIGN_JUSTIFY:
934 // What should we do here?
935 break;
938 // Shift the beginnings of the records on this line.
939 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
940 SWF::TextRecord& rec = _textRecords[i];
941 rec.setXOffset(rec.xOffset() + shift_right);
943 return shift_right;
946 boost::intrusive_ptr<const Font>
947 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
949 if (newfont == _font) return _font;
951 boost::intrusive_ptr<const Font> oldfont = _font;
952 set_invalidated();
953 _font = newfont;
954 format_text();
955 return oldfont;
959 void
960 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
962 // tab (ASCII HT)
963 const int space = 32;
964 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
965 if (index == -1) {
966 IF_VERBOSE_MALFORMED_SWF (
967 log_error(_("TextField: missing glyph for space char (needed "
968 "for TAB). Make sure DisplayObject shapes for font "
969 "%s are being exported into your SWF file."),
970 rec.getFont()->name());
973 else {
974 // TODO: why is there a copy of the vector?
975 std::vector<int> tabStops = _tabStops;
977 std::sort(_tabStops.begin(), _tabStops.end());
979 int tab = 0;
980 if (!_tabStops.empty()) {
981 tab = _tabStops.back() + 1;
983 for (size_t i = 0; i < tabStops.size(); ++i) {
984 if (tabStops[i] > x) {
985 if((tabStops[i] - x) < tab) {
986 tab = tabStops[i] - x;
992 // This is necessary in case the number of tabs in the text
993 // are more than the actual number of tabStops inside the
994 // vector
995 if (tab != _tabStops.back() + 1) {
996 SWF::TextRecord::GlyphEntry ge;
997 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
998 ge.advance = tab;
999 rec.addGlyph(ge);
1000 x+=ge.advance;
1003 else {
1004 SWF::TextRecord::GlyphEntry ge;
1005 ge.index = index;
1006 ge.advance = scale * rec.getFont()->get_advance(index,
1007 _embedFonts);
1009 const int tabstop = 4;
1010 rec.addGlyph(ge, tabstop);
1011 x += ge.advance * tabstop;
1016 void
1017 TextField::format_text()
1019 _textRecords.clear();
1020 _line_starts.clear();
1021 _recordStarts.clear();
1022 _glyphcount = 0;
1024 _recordStarts.push_back(0);
1026 // nothing more to do if text is empty
1027 if (_text.empty()) {
1028 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1029 // not sure we should...
1030 reset_bounding_box(0, 0);
1031 return;
1034 LineStarts::iterator linestartit = _line_starts.begin();
1035 LineStarts::const_iterator linestartend = _line_starts.end();
1037 AutoSize autoSize = getAutoSize();
1038 if (autoSize != AUTOSIZE_NONE) {
1039 // When doing WordWrap we don't want to change
1040 // the boundaries. See bug #24348
1041 if (!doWordWrap()) {
1042 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1046 // FIXME: I don't think we should query the definition
1047 // to find the appropriate font to use, as ActionScript
1048 // code should be able to change the font of a TextField
1049 if (!_font) {
1050 log_error(_("No font for TextField!"));
1051 return;
1054 boost::uint16_t fontHeight = getFontHeight();
1055 float scale = fontHeight /
1056 static_cast<float>(_font->unitsPerEM(_embedFonts));
1057 const float fontLeading = _font->leading() * scale;
1058 const boost::uint16_t leftMargin = getLeftMargin();
1059 const boost::uint16_t indent = getIndent();
1060 const boost::uint16_t blockIndent = getBlockIndent();
1061 const bool underlined = getUnderlined();
1063 /// Remember the current bounds for autosize.
1064 SWFRect oldBounds(_bounds);
1066 SWF::TextRecord rec; // one to work on
1067 rec.setFont(_font.get());
1068 rec.setUnderline(underlined);
1069 rec.setColor(getTextColor());
1070 rec.setXOffset(PADDING_TWIPS +
1071 std::max(0, leftMargin + indent + blockIndent));
1072 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1073 rec.setTextHeight(fontHeight);
1075 // create in textrecord.h
1076 rec.setURL(_url);
1077 rec.setTarget(_target);
1079 // BULLET CASE:
1081 // First, we indent 10 spaces, and then place the bullet
1082 // character (in this case, an asterisk), then we pad it
1083 // again with 10 spaces
1084 // Note: this works only for additional lines of a
1085 // bulleted list, so that is why there is a bullet format
1086 // in the beginning of format_text()
1087 if (_bullet) {
1088 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1090 SWF::TextRecord::GlyphEntry ge;
1091 ge.index = space;
1092 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1093 rec.addGlyph(ge, 5);
1095 // We use an asterisk instead of a bullet
1096 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1097 ge.index = bullet;
1098 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1099 rec.addGlyph(ge);
1101 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1102 ge.index = space;
1103 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1104 rec.addGlyph(ge, 4);
1107 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1108 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1110 // Start the bbox at the upper-left corner of the first glyph.
1111 //reset_bounding_box(x, y + fontHeight);
1113 int last_code = -1; // only used if _embedFonts
1114 int last_space_glyph = -1;
1115 size_t last_line_start_record = 0;
1117 _line_starts.push_back(0);
1119 // String iterators are very sensitive to
1120 // potential changes to the string (to allow for copy-on-write).
1121 // So there must be no external changes to the string or
1122 // calls to most non-const member functions during this loop.
1123 // Especially not c_str() or data().
1124 std::wstring::const_iterator it = _text.begin();
1125 const std::wstring::const_iterator e = _text.end();
1127 ///handleChar takes care of placing the glyphs
1128 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1129 last_line_start_record);
1131 // Expand bounding box to include the whole text (if autoSize and wordWrap
1132 // is not in operation.
1133 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1135 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1137 if (_autoSize == AUTOSIZE_RIGHT) {
1138 /// Autosize right expands from the previous right margin.
1139 SWFMatrix m;
1141 m.set_x_translation(oldBounds.get_x_max() - _bounds.width());
1142 m.transform(_bounds);
1144 else if (_autoSize == AUTOSIZE_CENTER) {
1145 // Autosize center expands from the previous center.
1146 SWFMatrix m;
1147 m.set_x_translation(oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1148 _bounds.width() / 2.0);
1149 m.transform(_bounds);
1153 // Add the last line to our output.
1154 _textRecords.push_back(rec);
1156 // align the last (or single) line
1157 align_line(getTextAlignment(), last_line_start_record, x);
1159 scrollLines();
1161 set_invalidated(); //redraw
1165 void
1166 TextField::scrollLines()
1168 boost::uint16_t fontHeight = getFontHeight();
1169 float scale = fontHeight /
1170 static_cast<float>(_font->unitsPerEM(_embedFonts));
1171 float fontLeading = _font->leading() * scale;
1172 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1173 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1174 size_t manylines = _line_starts.size();
1175 size_t lastvisibleline = _scroll + _linesindisplay;
1176 size_t line = 0;
1178 // If there aren't as many lines as we have scrolled, display the
1179 // end of the text.
1180 if (manylines < _scroll) {
1181 _scroll = manylines - _linesindisplay;
1182 return;
1185 // which line is the cursor on?
1186 while (line < manylines && _line_starts[line] <= m_cursor) {
1187 ++line;
1190 if (manylines - _scroll <= _linesindisplay) {
1191 // This is for if we delete a line
1192 if (manylines < _linesindisplay) _scroll = 0;
1193 else {
1194 _scroll = manylines - _linesindisplay;
1196 } else if (line < _scroll) {
1197 //if we are at a higher position, scroll the lines down
1198 _scroll -= _scroll - line;
1199 } else if (manylines > _scroll + _linesindisplay) {
1200 //if we are at a lower position, scroll the lines up
1201 if (line >= (_scroll+_linesindisplay)) {
1202 _scroll += line - (lastvisibleline);
1208 void
1209 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1210 SWF::TextRecord& rec, int& last_space_glyph,
1211 LineStarts::value_type& last_line_start_record, float div)
1213 // newline.
1214 LineStarts::iterator linestartit = _line_starts.begin();
1215 LineStarts::const_iterator linestartend = _line_starts.end();
1217 float scale = _fontHeight /
1218 static_cast<float>(_font->unitsPerEM(_embedFonts));
1219 float fontLeading = _font->leading() * scale;
1220 float leading = getLeading();
1221 leading += fontLeading * scale; // not sure this is correct...
1223 // Close out this stretch of glyphs.
1224 ++_glyphcount;
1225 _textRecords.push_back(rec);
1226 _recordStarts.push_back(_glyphcount);
1227 align_line(getTextAlignment(), last_line_start_record, x);
1229 // Expand bounding box to include last column of text ...
1230 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1231 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1234 // new paragraphs get the indent.
1235 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1236 PADDING_TWIPS;
1237 y += div * (getFontHeight() + leading);
1238 if (y >= _bounds.height()) {
1239 ++_maxScroll;
1242 // Start a new record on the next line. Other properties of the
1243 // TextRecord should be left unchanged.
1244 rec.clearGlyphs();
1245 rec.setXOffset(x);
1246 rec.setYOffset(y);
1248 last_space_glyph = -1;
1249 last_line_start_record = _textRecords.size();
1251 linestartit = _line_starts.begin();
1252 linestartend = _line_starts.end();
1253 //Fit a line_start in the correct place
1254 const size_t currentPos = _glyphcount;
1256 while (linestartit < linestartend && *linestartit < currentPos)
1258 ++linestartit;
1260 _line_starts.insert(linestartit, currentPos);
1262 // BULLET CASE:
1264 // First, we indent 10 spaces, and then place the bullet
1265 // character (in this case, an asterisk), then we pad it
1266 // again with 10 spaces
1267 // Note: this works only for additional lines of a
1268 // bulleted list, so that is why there is a bullet format
1269 // in the beginning of format_text()
1270 if (_bullet)
1272 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1273 SWF::TextRecord::GlyphEntry ge;
1274 ge.index = space;
1275 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1277 rec.addGlyph(ge,5);
1278 _glyphcount += 5;
1280 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1281 ge.index = bullet;
1282 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1283 rec.addGlyph(ge);
1284 ++_glyphcount;
1286 ge.index = space;
1287 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1289 rec.addGlyph(ge,4);
1290 _glyphcount += 4;
1294 void
1295 TextField::handleChar(std::wstring::const_iterator& it,
1296 const std::wstring::const_iterator& e, boost::int32_t& x,
1297 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1298 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1300 LineStarts::iterator linestartit = _line_starts.begin();
1301 LineStarts::const_iterator linestartend = _line_starts.end();
1303 float scale = _fontHeight /
1304 static_cast<float>(_font->unitsPerEM(_embedFonts));
1305 float fontDescent = _font->descent(_embedFonts) * scale;
1306 float fontLeading = _font->leading() * scale;
1307 float leading = getLeading();
1308 leading += fontLeading * scale; // not sure this is correct...
1310 boost::uint32_t code = 0;
1311 while (it != e)
1313 code = *it++;
1314 if (!code) break;
1316 if ( _embedFonts )
1318 x += rec.getFont()->get_kerning_adjustment(last_code,
1319 static_cast<int>(code)) * scale;
1320 last_code = static_cast<int>(code);
1323 // Expand the bounding-box to the lower-right corner of each glyph as
1324 // we generate it.
1325 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1326 switch (code)
1328 case 27:
1329 // Ignore escape
1330 break;
1331 case 9:
1332 insertTab(rec, x, scale);
1333 break;
1334 case 8:
1335 // Backspace
1337 // This is a limited hack to enable overstrike effects.
1338 // It backs the cursor up by one DisplayObject and then continues
1339 // the layout. E.g. you can use this to display an underline
1340 // cursor inside a simulated text-entry box.
1342 // ActionScript understands the '\b' escape sequence
1343 // for inserting a BS DisplayObject.
1345 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1346 // OVER NEWLINES, ETC.
1348 if (!rec.glyphs().empty())
1350 // Peek at the previous glyph, and zero out its advance
1351 // value, so the next char overwrites it.
1352 float advance = rec.glyphs().back().advance;
1353 x -= advance;
1354 // Remove one glyph
1355 rec.clearGlyphs(1);
1357 continue;
1358 case 13:
1359 case 10:
1361 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1362 break;
1364 case '<':
1365 if (doHtml())
1367 //close out this stretch of glyphs
1368 _textRecords.push_back(rec);
1369 rec.clearGlyphs();
1370 _recordStarts.push_back(_glyphcount);
1371 if (*it == '/') {
1372 while (it != e && *it != '>') {
1373 ++it;
1375 ++it;
1376 return;
1378 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1379 "gnash will just ignore the tags and "
1380 "print their content")));
1382 std::wstring discard;
1383 std::map<std::string,std::string> attributes;
1384 SWF::TextRecord newrec;
1385 newrec.setFont(rec.getFont());
1386 newrec.setUnderline(rec.underline());
1387 newrec.setColor(rec.color());
1388 newrec.setTextHeight(rec.textHeight());
1389 newrec.setXOffset(x);
1390 newrec.setYOffset(y);
1391 bool selfclosing = false;
1392 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1393 std::string s(discard.begin(), discard.end());
1395 std::map<std::string,std::string>::const_iterator attloc;
1397 if (!complete) {
1398 //parsing went wrong
1399 continue;
1400 } else {
1401 // Don't think this is the best way to match with
1402 // tags...
1403 // TODO: assumes tags are properly nested. This isn't
1404 // correct.
1405 if (s == "U") {
1406 //underline
1407 newrec.setUnderline(true);
1408 handleChar(it, e, x, y, newrec, last_code,
1409 last_space_glyph, last_line_start_record);
1411 else if (s == "A") {
1412 // anchor (blue text).
1413 rgba color(0, 0, 0xff, 0xff);
1414 newrec.setColor(color);
1415 newrec.setUnderline(true);
1416 attloc = attributes.find("HREF");
1417 if (attloc != attributes.end()) {
1418 newrec.setURL(attloc->second);
1420 attloc = attributes.find("TARGET");
1421 if (attloc !=attributes.end()) {
1422 newrec.setTarget(attloc->second);
1424 handleChar(it, e, x, y, newrec, last_code,
1425 last_space_glyph, last_line_start_record);
1427 else if (s == "B") {
1428 //bold
1429 Font* boldfont = new Font(rec.getFont()->name(),
1430 true, rec.getFont()->isItalic());
1431 newrec.setFont(boldfont);
1432 handleChar(it, e, x, y, newrec, last_code,
1433 last_space_glyph, last_line_start_record);
1435 else if (s == "FONT") {
1436 //font
1437 boost::uint16_t originalsize = _fontHeight;
1438 attloc = attributes.find("COLOR");
1439 if (attloc != attributes.end()) {
1440 std::string hexval(attloc->second);
1441 if (hexval.empty() || hexval[0] != '#') {
1442 // FIXME: should this be a log_aserror
1443 // or log_unimpl ? It is triggered
1444 // by TextFieldHTML.as
1445 log_error("Unexpected value '%s' in "
1446 "TextField font color attribute",
1447 hexval);
1449 else {
1450 hexval.erase(0, 1);
1451 // font COLOR attribute
1452 const rgba color =
1453 colorFromHexString(hexval);
1454 newrec.setColor(color);
1457 attloc = attributes.find("FACE");
1458 if (attloc != attributes.end()) {
1459 //font FACE attribute
1460 Font* newfont = new Font(attloc->second,
1461 rec.getFont()->isBold(), rec.getFont()->isItalic());
1462 newrec.setFont(newfont);
1464 attloc = attributes.find("SIZE");
1465 if (attloc != attributes.end()) {
1466 //font SIZE attribute
1467 std::string firstchar = attloc->second.substr(0,1);
1468 if (firstchar == "+") {
1469 newrec.setTextHeight(rec.textHeight() +
1471 (pixelsToTwips(std::strtol(
1472 attloc->second.substr(1,attloc->second.length()-1).data(),
1473 NULL,10))));
1474 newrec.setYOffset(PADDING_TWIPS +
1475 newrec.textHeight() +
1476 (fontLeading - fontDescent));
1477 _fontHeight += pixelsToTwips(std::strtol(
1478 attloc->second.substr(1,attloc->second.length()-1).data(),
1479 NULL,10));
1480 } else if (firstchar == "-") {
1481 newrec.setTextHeight(rec.textHeight() -
1482 (pixelsToTwips(std::strtol(
1483 attloc->second.substr(1,attloc->second.length()-1).data(),
1484 NULL,10))));
1485 newrec.setYOffset(PADDING_TWIPS +
1486 newrec.textHeight() +
1487 (fontLeading - fontDescent));
1488 _fontHeight -= pixelsToTwips(std::strtol(
1489 attloc->second.substr(1,attloc->second.length()-1).data(),
1490 NULL,10));
1491 } else {
1492 newrec.setTextHeight(pixelsToTwips(std::strtol(
1493 attloc->second.data(), NULL, 10)));
1494 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1495 (fontLeading - fontDescent));
1496 _fontHeight = pixelsToTwips(std::strtol(
1497 attloc->second.data(), NULL, 10));
1500 handleChar(it, e, x, y, newrec, last_code,
1501 last_space_glyph, last_line_start_record);
1502 _fontHeight = originalsize;
1503 y = newrec.yOffset();
1505 else if (s == "IMG") {
1506 //image
1507 log_unimpl("<img> html tag in TextField");
1508 handleChar(it, e, x, y, newrec, last_code,
1509 last_space_glyph, last_line_start_record);
1511 else if (s == "I") {
1512 //italic
1513 Font* italicfont = new Font(rec.getFont()->name(),
1514 rec.getFont()->isBold(), true);
1515 newrec.setFont(italicfont);
1516 handleChar(it, e, x, y, newrec, last_code,
1517 last_space_glyph, last_line_start_record);
1518 } else if (s == "LI") {
1519 //list item (bullet)
1520 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1521 SWF::TextRecord::GlyphEntry ge;
1522 ge.index = space;
1523 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1524 newrec.addGlyph(ge, 5);
1526 // We use an asterisk instead of a bullet
1527 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1528 ge.index = bullet;
1529 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1530 newrec.addGlyph(ge);
1532 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1533 ge.index = space;
1534 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1535 newrec.addGlyph(ge, 4);
1537 handleChar(it, e, x, y, newrec, last_code,
1538 last_space_glyph, last_line_start_record);
1539 newLine(x, y, newrec, last_space_glyph,
1540 last_line_start_record, 1.0);
1542 else if (s == "SPAN") {
1543 //span
1544 log_unimpl("<span> html tag in TextField");
1545 handleChar(it, e, x, y, newrec, last_code,
1546 last_space_glyph, last_line_start_record);
1548 else if (s == "TEXTFORMAT") {
1549 log_debug("in textformat");
1550 //textformat
1551 boost::uint16_t originalblockindent = getBlockIndent();
1552 boost::uint16_t originalindent = getIndent();
1553 boost::uint16_t originalleading = getLeading();
1554 boost::uint16_t originalleftmargin = getLeftMargin();
1555 boost::uint16_t originalrightmargin = getRightMargin();
1556 std::vector<int> originaltabstops = getTabStops();
1557 attloc = attributes.find("BLOCKINDENT");
1558 if (attloc != attributes.end()) {
1559 //textformat BLOCKINDENT attribute
1560 setBlockIndent(pixelsToTwips(std::strtol(
1561 attloc->second.data(), NULL, 10)));
1562 if (newrec.xOffset() == std::max(0, originalleftmargin +
1563 originalindent + originalblockindent) + PADDING_TWIPS) {
1564 //if beginning of line, indent
1565 x = std::max(0, getLeftMargin() +
1566 getIndent() + getBlockIndent())
1567 + PADDING_TWIPS;
1568 newrec.setXOffset(x);
1571 attloc = attributes.find("INDENT");
1572 if (attloc != attributes.end()) {
1573 //textformat INDENT attribute
1574 setIndent(pixelsToTwips(std::strtol(
1575 attloc->second.data(), NULL, 10)));
1576 if (newrec.xOffset() == std::max(0, originalleftmargin +
1577 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1578 //if beginning of line, indent
1579 x = std::max(0, getLeftMargin() +
1580 getIndent() + getBlockIndent())
1581 + PADDING_TWIPS;
1582 newrec.setXOffset(x);
1585 attloc = attributes.find("LEADING");
1586 if (attloc != attributes.end()) {
1587 //textformat LEADING attribute
1588 setLeading(pixelsToTwips(std::strtol(
1589 attloc->second.data(), NULL, 10)));
1591 attloc = attributes.find("LEFTMARGIN");
1592 if (attloc != attributes.end()) {
1593 //textformat LEFTMARGIN attribute
1594 setLeftMargin(pixelsToTwips(std::strtol(
1595 attloc->second.data(), NULL, 10)));
1596 if (newrec.xOffset() == std::max(0, originalleftmargin +
1597 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1598 //if beginning of line, indent
1599 x = std::max(0, getLeftMargin() +
1600 getIndent() + getBlockIndent())
1601 + PADDING_TWIPS;
1602 newrec.setXOffset(x);
1605 attloc = attributes.find("RIGHTMARGIN");
1606 if (attloc != attributes.end()) {
1607 //textformat RIGHTMARGIN attribute
1608 setRightMargin(pixelsToTwips(std::strtol(
1609 attloc->second.data(), NULL, 10)));
1610 //FIXME:Should not apply this to this line if we are not at
1611 //beginning of line. Not sure how to do that.
1613 attloc = attributes.find("TABSTOPS");
1614 if (attloc != attributes.end()) {
1615 //textformat TABSTOPS attribute
1616 log_unimpl("html <textformat> tag tabstops attribute");
1618 handleChar(it, e, x, y, newrec, last_code,
1619 last_space_glyph, last_line_start_record);
1620 setBlockIndent(originalblockindent);
1621 setIndent(originalindent);
1622 setLeading(originalleading);
1623 setLeftMargin(originalleftmargin);
1624 setRightMargin(originalrightmargin);
1625 setTabStops(originaltabstops);
1627 else if (s == "P") {
1628 //paragraph
1629 if (_display == TEXTFORMAT_BLOCK) {
1630 handleChar(it, e, x, y, newrec, last_code,
1631 last_space_glyph,
1632 last_line_start_record);
1633 newLine(x, y, rec, last_space_glyph,
1634 last_line_start_record, 1.0);
1635 newLine(x, y, rec, last_space_glyph,
1636 last_line_start_record, 1.5);
1638 else {
1639 handleChar(it, e, x, y, newrec, last_code,
1640 last_space_glyph,
1641 last_line_start_record);
1644 else if (s == "BR" || s == "SBR") {
1645 //line break
1646 newLine(x, y, rec, last_space_glyph,
1647 last_line_start_record, 1.0);
1649 else {
1650 log_debug("<%s> tag is unsupported", s);
1651 if (!selfclosing) { //then recurse, look for closing tag
1652 handleChar(it, e, x, y, newrec, last_code,
1653 last_space_glyph, last_line_start_record);
1657 rec.setXOffset(x);
1658 rec.setYOffset(y);
1659 continue;
1661 // If HTML isn't enabled, carry on and insert the glyph.
1662 // FIXME: do we also want to be changing last_space_glyph?
1663 // ...because we are...
1664 case 32:
1665 last_space_glyph = rec.glyphs().size();
1666 // Don't break, as we still need to insert the space glyph.
1668 default:
1673 if ( password() )
1675 SWF::TextRecord::GlyphEntry ge;
1676 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1677 ge.index = bullet;
1678 ge.advance = scale * rec.getFont()->get_advance(bullet,
1679 _embedFonts);
1680 rec.addGlyph(ge);
1681 ++_glyphcount;
1682 break;
1684 // The font table holds up to 65535 glyphs. Casting
1685 // from uint32_t would, in the event that the code
1686 // is higher than 65535, result in the wrong DisplayObject
1687 // being chosen. Flash can currently only handle 16-bit
1688 // values.
1689 int index = rec.getFont()->get_glyph_index(
1690 static_cast<boost::uint16_t>(code), _embedFonts);
1692 IF_VERBOSE_MALFORMED_SWF (
1693 if (index == -1)
1695 // Missing glyph! Log the first few errors.
1696 static int s_log_count = 0;
1697 if (s_log_count < 10)
1699 s_log_count++;
1700 if (_embedFonts)
1702 log_swferror(_("TextField: missing embedded "
1703 "glyph for char %d. Make sure DisplayObject "
1704 "shapes for font %s are being exported "
1705 "into your SWF file"),
1706 code, _font->name());
1708 else
1710 log_swferror(_("TextField: missing device "
1711 "glyph for char %d. Maybe you don't have "
1712 "font '%s' installed in your system."),
1713 code, _font->name());
1717 // Drop through and use index == -1; this will display
1718 // using the empty-box glyph
1722 SWF::TextRecord::GlyphEntry ge;
1723 ge.index = index;
1724 ge.advance = scale * rec.getFont()->get_advance(index,
1725 _embedFonts);
1727 rec.addGlyph(ge);
1729 x += ge.advance;
1730 ++_glyphcount;
1734 float width = _bounds.width();
1735 if (x >= width - getRightMargin() - PADDING_TWIPS)
1737 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1738 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1739 getTarget(), _bounds);
1740 #endif
1742 // No wrap and no resize: truncate
1743 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1745 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1746 log_debug(" wordWrap=false, autoSize=none");
1747 #endif
1748 // Truncate long line, but keep expanding text box
1749 bool newlinefound = false;
1750 while (it != e)
1752 code = *it++;
1753 if (_embedFonts)
1755 x += rec.getFont()->get_kerning_adjustment(last_code,
1756 static_cast<int>(code)) * scale;
1757 last_code = code;
1759 // Expand the bounding-box to the lower-right corner
1760 // of each glyph, even if we don't display it
1761 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1762 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1763 log_debug("Text bbox expanded to %s (width: %f)",
1764 m_text_bounding_box, m_text_bounding_box.width());
1765 #endif
1767 if (code == 13 || code == 10)
1769 newlinefound = true;
1770 break;
1773 int index = rec.getFont()->get_glyph_index(
1774 static_cast<boost::uint16_t>(code), _embedFonts);
1775 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1778 if (!newlinefound) break;
1780 else if (doWordWrap()) {
1782 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1783 log_debug(" wordWrap=true");
1784 #endif
1786 // Insert newline if there's space or autosize != none
1788 // Close out this stretch of glyphs.
1789 _textRecords.push_back(rec);
1791 float previous_x = x;
1792 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1793 y += _fontHeight + leading;
1794 if (y >= _bounds.height()) {
1795 ++_maxScroll;
1798 // Start a new record on the next line.
1799 rec.clearGlyphs();
1800 rec.setXOffset(x);
1801 rec.setYOffset(y);
1803 // TODO : what if m_text_glyph_records is empty ?
1804 // Is it possible ?
1805 assert(!_textRecords.empty());
1806 SWF::TextRecord& last_line = _textRecords.back();
1808 linestartit = _line_starts.begin();
1809 linestartend = _line_starts.end();
1810 if (last_space_glyph == -1)
1812 // Pull the previous glyph down onto the
1813 // new line.
1814 if (!last_line.glyphs().empty())
1816 rec.addGlyph(last_line.glyphs().back());
1817 x += last_line.glyphs().back().advance;
1818 previous_x -= last_line.glyphs().back().advance;
1819 last_line.clearGlyphs(1);
1820 //record the new line start
1822 const size_t currentPos = _glyphcount;
1823 while (linestartit != linestartend &&
1824 *linestartit + 1 <= currentPos)
1826 linestartit++;
1828 _line_starts.insert(linestartit, currentPos);
1829 _recordStarts.push_back(currentPos);
1831 } else {
1832 // Move the previous word down onto the next line.
1834 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1836 const SWF::TextRecord::Glyphs::size_type lineSize =
1837 last_line.glyphs().size();
1838 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1839 ++i)
1841 rec.addGlyph(last_line.glyphs()[i]);
1842 x += last_line.glyphs()[i].advance;
1843 previous_x -= last_line.glyphs()[i].advance;
1845 last_line.clearGlyphs(lineSize - last_space_glyph);
1847 // record the position at the start of this line as
1848 // a line_start
1849 const size_t linestartpos = _glyphcount -
1850 rec.glyphs().size();
1852 while (linestartit < linestartend &&
1853 *linestartit < linestartpos)
1855 ++linestartit;
1857 _line_starts.insert(linestartit, linestartpos);
1858 _recordStarts.push_back(linestartpos);
1861 align_line(getTextAlignment(), last_line_start_record, previous_x);
1863 last_space_glyph = -1;
1864 last_line_start_record = _textRecords.size();
1867 else
1869 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1870 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1871 #endif
1878 TextField::getDefinitionVersion() const
1880 // TODO: work out if this correct.
1881 return get_root()->getDefinitionVersion();
1885 TextField::VariableRef
1886 TextField::parseTextVariableRef(const std::string& variableName) const
1888 VariableRef ret;
1889 ret.first = 0;
1891 #ifdef DEBUG_DYNTEXT_VARIABLES
1892 log_debug(_("VariableName: %s"), variableName);
1893 #endif
1895 /// Why isn't get_environment const again ?
1896 const as_environment& env = const_cast<TextField*>(this)->get_environment();
1898 as_object* target = getObject(env.target());
1899 if (!target) {
1900 IF_VERBOSE_MALFORMED_SWF(
1901 log_swferror(_("Current environment has no target, "
1902 "can't bind VariableName (%s) associated to "
1903 "text field. Gnash will try to register "
1904 "again on next access."), variableName);
1906 return ret;
1909 // If the variable string contains a path, we extract
1910 // the appropriate target from it and update the variable
1911 // name. We copy the string so we can assign to it if necessary.
1912 std::string parsedName = variableName;
1913 std::string path, var;
1914 if (parsePath(variableName, path, var)) {
1915 #ifdef DEBUG_DYNTEXT_VARIABLES
1916 log_debug(_("Variable text Path: %s, Var: %s"), path, var);
1917 #endif
1918 // find target for the path component
1919 // we use our parent's environment for this
1920 target = findObject(env, path);
1922 parsedName = var;
1925 if (!target) {
1926 IF_VERBOSE_MALFORMED_SWF(
1927 log_swferror(_("VariableName associated to text field refers "
1928 "to an unknown target (%s). It is possible that the "
1929 "DisplayObject will be instantiated later in the SWF "
1930 "stream. Gnash will try to register again on next "
1931 "access."), path);
1933 return ret;
1936 ret.first = target;
1937 ret.second = getURI(getVM(*object()), parsedName);
1939 return ret;
1942 void
1943 TextField::registerTextVariable()
1945 //#define DEBUG_DYNTEXT_VARIABLES 1
1947 #ifdef DEBUG_DYNTEXT_VARIABLES
1948 log_debug(_("registerTextVariable() called"));
1949 #endif
1951 if (_text_variable_registered) {
1952 return;
1955 if (_variable_name.empty()) {
1956 _text_variable_registered = true;
1957 return;
1960 VariableRef varRef = parseTextVariableRef(_variable_name);
1961 as_object* target = varRef.first;
1962 if (!target) {
1963 log_debug(_("VariableName associated to text field (%s) refer to "
1964 "an unknown target. It is possible that the DisplayObject "
1965 "will be instantiated later in the SWF stream. "
1966 "Gnash will try to register again on next access."),
1967 _variable_name);
1968 return;
1971 const ObjectURI& key = varRef.second;
1972 as_object* obj = getObject(this);
1973 const int version = getSWFVersion(*obj);
1975 // check if the VariableName already has a value,
1976 // in that case update text value
1977 as_value val;
1978 if (target->get_member(key, &val)) {
1979 // TODO: pass environment to to_string ?
1980 // as_environment& env = get_environment();
1981 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
1983 else if (_textDefined) {
1984 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
1985 target->set_member(key, newVal);
1988 MovieClip* sprite = get<MovieClip>(target);
1990 if (sprite) {
1991 // add the textfield variable to the target sprite
1992 // TODO: have set_textfield_variable take a string_table::key instead ?
1993 sprite->set_textfield_variable(key, this);
1996 _text_variable_registered = true;
1999 /// Parses an HTML tag (between < and >) and puts
2000 /// the contents into tag. Returns false if the
2001 /// tag was incomplete. The iterator is moved to after
2002 /// the closing tag or the end of the string.
2003 bool
2004 TextField::parseHTML(std::wstring& tag,
2005 std::map<std::string, std::string>& attributes,
2006 std::wstring::const_iterator& it,
2007 const std::wstring::const_iterator& e,
2008 bool& selfclosing) const
2010 while (it != e && *it != ' ') {
2011 if (*it == '/') {
2012 ++it;
2013 if (*it == '>') {
2014 ++it;
2015 selfclosing = true;
2016 return true;
2017 } else {
2018 while (it != e) {
2019 ++it;
2021 log_error("invalid html tag");
2022 return false;
2025 if (*it == '>') {
2026 ++it;
2027 return true;
2030 // Check for NULL character
2031 if (*it == 0) {
2032 log_error("found NULL character in htmlText");
2033 return false;
2035 tag.push_back(std::toupper(*it));
2036 ++it;
2038 while (it != e && *it == ' ') {
2039 ++it; //skip over spaces
2041 if (*it == '>') {
2042 ++it;
2043 return true;
2045 if (*it == '/') {
2046 ++it;
2047 if (*it == '>') {
2048 ++it;
2049 selfclosing = true;
2050 return true;
2051 } else {
2052 while (it != e) {
2053 ++it;
2055 log_error("invalid html tag");
2056 return false;
2060 std::string attname;
2061 std::string attvalue;
2063 //attributes
2064 while (it != e && *it != '>') {
2065 while (it != e && *it != '=' && *it != ' ') {
2067 if (*it == 0) {
2068 log_error("found NULL character in htmlText");
2069 return false;
2071 if (*it == '>') {
2072 log_error("malformed HTML tag, invalid attribute name");
2073 while (it != e) {
2074 ++it;
2076 return false;
2079 attname.push_back(std::toupper(*it));
2080 ++it;
2082 while (it != e && (*it == ' ' || *it == '=')) {
2083 ++it; //skip over spaces and '='
2086 if (it == e) return false;
2087 const char q = *it;
2088 if (q != '"' && q != '\'') {
2089 // This is not an attribute.
2090 while (it != e) ++it;
2091 return false;
2094 // Advance past attribute opener
2095 ++it;
2096 while (it != e && *it != q) {
2098 if (*it == 0) {
2099 log_error("found NULL character in htmlText");
2100 return false;
2103 attvalue.push_back(std::toupper(*it));
2104 ++it;
2107 if (it == e) return false;
2109 if (*it != q) {
2110 while (it != e) ++it;
2111 return false;
2114 // Skip attribute closer.
2115 ++it;
2117 attributes.insert(std::make_pair(attname, attvalue));
2118 attname.clear();
2119 attvalue.clear();
2121 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2122 log_error("malformed HTML tag, invalid attribute value");
2123 while (it != e) {
2124 ++it;
2126 return false;
2128 if (*it == ' ') {
2129 while (it != e && *it == ' ') {
2130 ++it; //skip over spaces
2133 if (*it == '>') {
2134 ++it;
2135 return true;
2136 } else if (*it == '/') {
2137 ++it;
2138 if (*it == '>') {
2139 ++it;
2140 selfclosing = true;
2141 return true;
2142 } else {
2143 while (it != e) {
2144 ++it;
2146 log_error("invalid html tag");
2147 return false;
2152 #ifdef GNASH_DEBUG_TEXTFIELDS
2153 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2154 #endif
2155 log_error("I declare this a HTML syntax error");
2156 return false; //since we did not return already, must be malformed...?
2159 void
2160 TextField::set_variable_name(const std::string& newname)
2162 if (newname != _variable_name) {
2163 _variable_name = newname;
2165 // The name was empty or undefined, so there's nothing more to do.
2166 if (_variable_name.empty()) return;
2168 _text_variable_registered = false;
2170 #ifdef DEBUG_DYNTEXT_VARIABLES
2171 log_debug("Calling updateText after change of variable name");
2172 #endif
2174 // Use the original definition text if this isn't dynamically
2175 // created.
2176 if (_tag) updateText(_tag->defaultText());
2178 #ifdef DEBUG_DYNTEXT_VARIABLES
2179 log_debug("Calling registerTextVariable after change of variable "
2180 "name and updateText call");
2181 #endif
2182 registerTextVariable();
2186 bool
2187 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2189 const SWFMatrix wm = getWorldMatrix(*this).invert();
2190 point lp(x, y);
2191 wm.transform(lp);
2192 return _bounds.point_test(lp.x, lp.y);
2195 bool
2196 TextField::getDrawBorder() const
2198 return _drawBorder;
2201 void
2202 TextField::setDrawBorder(bool val)
2204 if (_drawBorder != val) {
2205 set_invalidated();
2206 _drawBorder = val;
2210 rgba
2211 TextField::getBorderColor() const
2213 return _borderColor;
2216 void
2217 TextField::setBorderColor(const rgba& col)
2219 if (_borderColor != col) {
2220 set_invalidated();
2221 _borderColor = col;
2225 bool
2226 TextField::getDrawBackground() const
2228 return _drawBackground;
2231 void
2232 TextField::setDrawBackground(bool val)
2234 if (_drawBackground != val) {
2235 set_invalidated();
2236 _drawBackground = val;
2240 rgba
2241 TextField::getBackgroundColor() const
2243 return _backgroundColor;
2246 void
2247 TextField::setBackgroundColor(const rgba& col)
2249 if (_backgroundColor != col) {
2250 set_invalidated();
2251 _backgroundColor = col;
2255 void
2256 TextField::setTextColor(const rgba& col)
2258 if (_textColor != col) {
2260 set_invalidated();
2261 _textColor = col;
2262 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2263 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2267 void
2268 TextField::setEmbedFonts(bool use)
2270 if (_embedFonts != use) {
2271 set_invalidated();
2272 _embedFonts=use;
2273 format_text();
2277 void
2278 TextField::setWordWrap(bool wrap)
2280 if (_wordWrap != wrap) {
2281 set_invalidated();
2282 _wordWrap = wrap;
2283 format_text();
2287 void
2288 TextField::setLeading(boost::int16_t h)
2290 if (_leading != h) {
2291 set_invalidated();
2292 _leading = h;
2296 void
2297 TextField::setUnderlined(bool v)
2299 if (_underlined != v) {
2300 set_invalidated();
2301 _underlined = v;
2305 void
2306 TextField::setBullet(bool b)
2308 if (_bullet != b) {
2309 _bullet = b;
2313 void
2314 TextField::setTabStops(const std::vector<int>& tabStops)
2316 _tabStops.resize(tabStops.size());
2318 for (size_t i = 0; i < tabStops.size(); i ++) {
2319 _tabStops[i] = pixelsToTwips(tabStops[i]);
2322 set_invalidated();
2325 void
2326 TextField::setURL(std::string url)
2328 if (_url != url) {
2329 set_invalidated();
2330 _url = url;
2334 void
2335 TextField::setTarget(std::string target)
2337 if (_target != target) {
2338 set_invalidated();
2339 _target = target;
2343 void
2344 TextField::setDisplay(TextFormatDisplay display)
2346 if (_display != display) {
2347 set_invalidated();
2348 _display = display;
2352 void
2353 TextField::setAlignment(TextAlignment h)
2355 if (_alignment != h) {
2356 set_invalidated();
2357 _alignment = h;
2361 void
2362 TextField::setIndent(boost::uint16_t h)
2364 if (_indent != h) {
2365 set_invalidated();
2366 _indent = h;
2370 void
2371 TextField::setBlockIndent(boost::uint16_t h)
2373 if (_blockIndent != h) {
2374 set_invalidated();
2375 _blockIndent = h;
2379 void
2380 TextField::setRightMargin(boost::uint16_t h)
2382 if (_rightMargin != h) {
2383 set_invalidated();
2384 _rightMargin = h;
2388 void
2389 TextField::setLeftMargin(boost::uint16_t h)
2391 if (_leftMargin != h) {
2392 set_invalidated();
2393 _leftMargin = h;
2397 void
2398 TextField::setFontHeight(boost::uint16_t h)
2400 if (_fontHeight != h) {
2401 set_invalidated();
2402 _fontHeight = h;
2407 TextField::TypeValue
2408 TextField::parseTypeValue(const std::string& val)
2410 StringNoCaseEqual cmp;
2412 if (cmp(val, "input")) return typeInput;
2413 if (cmp(val, "dynamic")) return typeDynamic;
2414 return typeInvalid;
2418 const char*
2419 TextField::typeValueName(TypeValue val)
2421 switch (val) {
2422 case typeInput:
2423 //log_debug("typeInput returned as 'input'");
2424 return "input";
2425 case typeDynamic:
2426 //log_debug("typeDynamic returned as 'dynamic'");
2427 return "dynamic";
2428 default:
2429 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2430 return "invalid";
2435 void
2436 TextField::setAutoSize(AutoSize val)
2438 if (val == _autoSize) return;
2440 set_invalidated();
2441 _autoSize = val;
2442 format_text();
2445 TextField::TextAlignment
2446 TextField::getTextAlignment()
2448 TextAlignment textAlignment = getAlignment();
2450 switch (_autoSize) {
2451 case AUTOSIZE_CENTER:
2452 textAlignment = ALIGN_CENTER;
2453 break;
2454 case AUTOSIZE_LEFT:
2455 textAlignment = ALIGN_LEFT;
2456 break;
2457 case AUTOSIZE_RIGHT:
2458 textAlignment = ALIGN_RIGHT;
2459 break;
2460 default:
2461 // Leave it as it was.
2462 break;
2465 return textAlignment;
2468 void
2469 TextField::onChanged()
2471 as_object* obj = getObject(this);
2472 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2475 /// This is called by movie_root when focus is applied to this TextField.
2477 /// The return value is true if the TextField can receive focus.
2478 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2479 /// handle focus.
2480 bool
2481 TextField::handleFocus()
2483 set_invalidated();
2485 /// Select the entire text on focus.
2486 setSelection(0, _text.length());
2488 m_has_focus = true;
2490 m_cursor = _text.size();
2491 format_text();
2492 return true;
2495 /// This is called by movie_root when focus is removed from the
2496 /// current TextField.
2497 void
2498 TextField::killFocus()
2500 if (!m_has_focus) return;
2501 set_invalidated();
2502 m_has_focus = false;
2503 format_text(); // is this needed ?
2506 void
2507 TextField::setWidth(double newwidth)
2509 const SWFRect& bounds = getBounds();
2510 _bounds.set_to_rect(bounds.get_x_min(),
2511 bounds.get_y_min(),
2512 bounds.get_x_min() + newwidth,
2513 bounds.get_y_max());
2516 void
2517 TextField::setHeight(double newheight)
2519 const SWFRect& bounds = getBounds();
2520 _bounds.set_to_rect(bounds.get_x_min(),
2521 bounds.get_y_min(),
2522 bounds.get_x_max(),
2523 bounds.get_y_min() + newheight);
2526 } // namespace gnash
2529 // Local Variables:
2530 // mode: C++
2531 // indent-tabs-mode: t
2532 // End: