fix typo, add instructions about lowercase named strings
[gnash.git] / libcore / TextField.cpp
blob1712574f86f59585c26013092d4ac10e8e6175c5
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 "utf8.h"
32 #include "log.h"
33 #include "swf/DefineEditTextTag.h"
34 #include "MovieClip.h"
35 #include "TextField.h"
36 #include "movie_root.h" // for killing focus
37 #include "as_environment.h"
38 #include "Font.h"
39 #include "fontlib.h"
40 #include "namedStrings.h"
41 #include "StringPredicates.h"
42 #include "TextFormat_as.h"
43 #include "GnashKey.h"
44 #include "TextRecord.h"
45 #include "Point2d.h"
46 #include "GnashNumeric.h"
47 #include "MouseButtonState.h"
48 #include "Global_as.h"
49 #include "Renderer.h"
50 #include "Transform.h"
52 #include <algorithm>
53 #include <string>
54 #include <boost/assign/list_of.hpp>
55 #include <boost/bind.hpp>
56 #include <cstdlib>
57 #include <cctype>
58 #include <utility>
59 #include <map>
61 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
62 #define PADDING_TWIPS 40
64 // Define the following to get detailed log information about
65 // textfield bounds and HTML tags:
66 //#define GNASH_DEBUG_TEXTFIELDS 1
68 // Define this to get debugging info about text formatting
69 //#define GNASH_DEBUG_TEXT_FORMATTING 1
71 namespace gnash {
73 TextField::TextField(as_object* object, DisplayObject* parent,
74 const SWF::DefineEditTextTag& def)
76 InteractiveObject(object, parent),
77 _tag(&def),
78 _url(""),
79 _target(""),
80 _display(),
81 _tabStops(),
82 _variable_name(def.variableName()),
83 _backgroundColor(255,255,255,255),
84 _borderColor(0,0,0,255),
85 _textColor(def.color()),
86 _alignment(def.alignment()),
87 _font(0),
88 m_cursor(0u),
89 _glyphcount(0u),
90 _scroll(0u),
91 _maxScroll(1u),
92 _hScroll(0u),
93 _maxHScroll(0u),
94 _bottomScroll(0u),
95 _linesindisplay(0u),
96 _maxChars(def.maxChars()),
97 _autoSize(def.autoSize() ? AUTOSIZE_LEFT : AUTOSIZE_NONE),
98 _type(def.readOnly() ? typeDynamic : typeInput),
99 _bounds(def.bounds()),
100 _selection(0, 0),
101 _leading(def.leading()),
102 _indent(def.indent()),
103 _blockIndent(0),
104 _leftMargin(def.leftMargin()),
105 _rightMargin(def.rightMargin()),
106 _fontHeight(def.textHeight()),
107 _textDefined(def.hasText()),
108 _htmlTextDefined(def.hasText()),
109 _restrictDefined(false),
110 _underlined(false),
111 _bullet(false),
112 m_has_focus(false),
113 _multiline(def.multiline()),
114 _password(def.password()),
115 _text_variable_registered(false),
116 _drawBackground(def.border()),
117 _drawBorder(def.border()),
118 _embedFonts(def.getUseEmbeddedGlyphs()),
119 _wordWrap(def.wordWrap()),
120 _html(def.html()),
121 _selectable(!def.noSelect())
124 assert(object);
126 // WARNING! remember to set the font *before* setting text value!
127 boost::intrusive_ptr<const Font> f = def.getFont();
128 if (!f) f = fontlib::get_default_font();
129 setFont(f);
131 int version = getSWFVersion(*object);
133 // set default text *before* calling registerTextVariable
134 // (if the textvariable already exist and has a value
135 // the text will be replaced with it)
136 if (_textDefined)
138 setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
139 setHtmlTextValue(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 _htmlTextDefined(false),
180 _restrictDefined(false),
181 _underlined(false),
182 _bullet(false),
183 m_has_focus(false),
184 _multiline(false),
185 _password(false),
186 _text_variable_registered(false),
187 _drawBackground(false),
188 _drawBorder(false),
189 _embedFonts(false),
190 _wordWrap(false),
191 _html(false),
192 _selectable(true)
194 // Use the default font (Times New Roman for Windows, Times for Mac
195 // according to docs. They don't say what it is for Linux.
196 boost::intrusive_ptr<const Font> f = fontlib::get_default_font();
197 setFont(f);
199 init();
202 void
203 TextField::init()
205 registerTextVariable();
207 reset_bounding_box(0, 0);
211 TextField::~TextField()
215 void
216 TextField::removeTextField()
218 int depth = get_depth();
219 if ( depth < 0 || depth > 1048575 )
221 //IF_VERBOSE_ASCODING_ERRORS(
222 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
223 "out of the 'dynamic' zone [0..1048575], won't remove"),
224 getTarget(), depth);
225 //);
226 return;
229 DisplayObject* p = parent();
230 assert(p); // every TextField must have a parent, right ?
232 MovieClip* parentSprite = p->to_movie();
234 if (!parentSprite) {
235 log_error("FIXME: attempt to remove a TextField being a child of a %s",
236 typeName(*p));
237 return;
240 // second argument is arbitrary, see comments above
241 // the function declaration in MovieClip.h
242 parentSprite->remove_display_object(depth, 0);
245 void
246 TextField::show_cursor(Renderer& renderer, const SWFMatrix& mat)
248 if (_textRecords.empty()) {
249 return;
251 boost::uint16_t x;
252 boost::uint16_t y;
253 boost::uint16_t h;
254 size_t i = cursorRecord();
255 SWF::TextRecord record = _textRecords[i];
257 x = record.xOffset();
258 y = record.yOffset() - record.textHeight() + getLeading();
259 h = record.textHeight();
261 if (!record.glyphs().empty()) {
262 for (unsigned int p = 0 ; p < (m_cursor - _recordStarts[i]); ++p) {
263 x += record.glyphs()[p].advance;
267 const std::vector<point> box = boost::assign::list_of
268 (point(x, y))
269 (point(x, y + h));
271 renderer.drawLine(box, rgba(0, 0, 0, 255), mat);
274 size_t
275 TextField::cursorRecord()
277 SWF::TextRecord record;
278 size_t i = 0;
280 if (_textRecords.size() != 0) {
281 while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
282 ++i;
284 return i-1;
286 return 0;
289 void
290 TextField::display(Renderer& renderer, const Transform& base)
292 const DisplayObject::MaskRenderer mr(renderer, *this);
294 registerTextVariable();
296 const bool drawBorder = getDrawBorder();
297 const bool drawBackground = getDrawBackground();
299 Transform xform = base * transform();
301 // This is a hack to handle device fonts, which are not affected by
302 // color transform.
303 if (getEmbedFonts()) xform.colorTransform = SWFCxForm();
305 if ((drawBorder || drawBackground) && !_bounds.is_null()) {
307 std::vector<point> coords(4);
309 boost::int32_t xmin = _bounds.get_x_min();
310 boost::int32_t xmax = _bounds.get_x_max();
311 boost::int32_t ymin = _bounds.get_y_min();
312 boost::int32_t ymax = _bounds.get_y_max();
314 coords[0].setTo(xmin, ymin);
315 coords[1].setTo(xmax, ymin);
316 coords[2].setTo(xmax, ymax);
317 coords[3].setTo(xmin, ymax);
319 rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
320 rgba backgroundColor = drawBackground ? getBackgroundColor() :
321 rgba(0,0,0,0);
323 SWFCxForm cx = xform.colorTransform;
325 if (drawBorder) borderColor = cx.transform(borderColor);
327 if (drawBackground) backgroundColor = cx.transform(backgroundColor);
329 #ifdef GNASH_DEBUG_TEXTFIELDS
330 log_debug("rendering a Pol composed by corners %s", _bounds);
331 #endif
333 renderer.draw_poly(&coords.front(), 4, backgroundColor,
334 borderColor, xform.matrix, true);
338 // Draw our actual text.
339 // Using a SWFMatrix to translate to def bounds seems an hack to me.
340 // A cleaner implementation is likely correctly setting the
341 // _xOffset and _yOffset memebers in glyph records.
342 // Anyway, see bug #17954 for a testcase.
343 if (!_bounds.is_null()) {
344 xform.matrix.concatenate_translation(_bounds.get_x_min(),
345 _bounds.get_y_min());
348 _displayRecords.clear();
349 float scale = getFontHeight() /
350 static_cast<float>(_font->unitsPerEM(_embedFonts));
351 float fontLeading = _font->leading() * scale;
353 //offset the lines
354 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
355 size_t recordline;
356 for (size_t i = 0; i < _textRecords.size(); ++i) {
357 recordline = 0;
358 //find the line the record is on
359 while (recordline < _line_starts.size() &&
360 _line_starts[recordline] <= _recordStarts[i]) {
361 ++recordline;
363 //offset the line
364 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
365 //add the lines we want to the display record
366 if (_textRecords[i].yOffset() > 0 &&
367 _textRecords[i].yOffset() < _bounds.height()) {
368 _displayRecords.push_back(_textRecords[i]);
372 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
373 _embedFonts);
375 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
377 clear_invalidated();
381 void
382 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
384 if (!force && !invalidated()) return; // no need to redraw
386 ranges.add(m_old_invalidated_ranges);
388 const SWFMatrix& wm = getWorldMatrix(*this);
390 SWFRect bounds = getBounds();
391 bounds.expand_to_rect(m_text_bounding_box);
392 wm.transform(bounds);
393 ranges.add(bounds.getRange());
396 void
397 TextField::setRestrict(const std::string& restrict)
399 _restrictDefined = true;
401 std::string::const_iterator rit = restrict.begin();
402 std::string::const_iterator re = restrict.end();
403 std::set<wchar_t>::const_iterator locate;
405 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
406 for (unsigned int i = 0; i <= 255; ++i) {
407 _restrictedchars.insert(char(i));
409 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
410 _restrictedchars.clear();
413 while (rit != re) {
414 while (rit != re && *rit != '^') { //This loop allows chars
415 if (*rit == '-') {
416 log_error("invalid restrict string");
417 return;
418 } else if (*(rit+1) == '-') {
419 if (re - (rit+2) != 0) {
420 unsigned int q = *(rit+2);
421 for (unsigned int p = *rit; p <= q; (++p)){
422 _restrictedchars.insert(char(p));
424 rit += 3;
425 } else {
426 log_error("invalid restrict string");
427 return;
429 } else if (*rit == '\\') {
430 ++rit;
431 _restrictedchars.insert(*rit);
432 ++rit;
433 } else {
434 _restrictedchars.insert(*rit);
435 ++rit;
438 if (rit != re) {
439 ++rit;
441 while (rit != re && *rit != '^') { //This loop restricts chars
442 locate = _restrictedchars.find(*rit);
443 if (*rit == '-') {
444 log_error("invalid restrict string");
445 return;
446 } else if (*(rit+1) == '-') {
447 if (re - (rit+2) != 0) {
448 unsigned int q = *(rit+2);
449 for (unsigned int p = *rit; p <= q; ++p){
450 locate = _restrictedchars.find(p);
451 if(locate != _restrictedchars.end()) {
452 _restrictedchars.erase(locate);
455 ++rit;
456 ++rit;
457 ++rit;
458 } else {
459 log_error("invalid restrict string");
460 return;
462 } else if (*rit == '\\') {
463 ++rit;
464 locate = _restrictedchars.find(*rit);
465 if(locate != _restrictedchars.end()) {
466 _restrictedchars.erase(locate);
468 ++rit;
469 } else {
470 if(locate != _restrictedchars.end()) {
471 _restrictedchars.erase(locate);
473 ++rit;
476 if (rit != re) {
477 ++rit;
480 _restrict = restrict;
483 void
484 TextField::replaceSelection(const std::string& replace)
487 const int version = getSWFVersion(*getObject(this));
488 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
490 const size_t start = _selection.first;
491 const size_t replaceLength = wstr.size();
493 _text.replace(start, _selection.second - start, wstr);
494 _selection = std::make_pair(start + replaceLength, start + replaceLength);
497 void
498 TextField::setSelection(int start, int end)
501 if (_text.empty()) {
502 _selection = std::make_pair(0, 0);
503 return;
506 const size_t textLength = _text.size();
508 if (start < 0) start = 0;
509 else start = std::min<size_t>(start, textLength);
511 if (end < 0) end = 0;
512 else end = std::min<size_t>(end, textLength);
514 // The cursor position is always set to the end value, even if the
515 // two values are swapped to obtain the selection. Equal values are
516 // fine.
517 m_cursor = end;
518 if (start > end) std::swap(start, end);
520 _selection = std::make_pair(start, end);
523 void
524 TextField::notifyEvent(const event_id& ev)
526 switch (ev.id())
528 case event_id::PRESS:
530 movie_root& root = stage();
531 boost::int32_t x_mouse, y_mouse;
532 root.get_mouse_state(x_mouse, y_mouse);
534 SWFMatrix m = getMatrix(*this);
536 x_mouse -= m.get_x_translation();
537 y_mouse -= m.get_y_translation();
539 SWF::TextRecord rec;
541 for (size_t i=0; i < _textRecords.size(); ++i) {
542 if ((x_mouse > _textRecords[i].xOffset()) &&
543 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
544 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
545 (y_mouse < _textRecords[i].yOffset())) {
546 rec = _textRecords[i];
547 break;
551 if (!rec.getURL().empty()) {
552 root.getURL(rec.getURL(), rec.getTarget(), "",
553 MovieClip::METHOD_NONE);
556 break;
558 case event_id::KEY_PRESS:
560 setHtml(false); //editable html fields are not yet implemented
561 std::wstring s = _text;
563 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
564 // The maximum value is about 265, including function keys.
565 // It seems that typing in DisplayObjects outside the Latin-1 set
566 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
567 // is not supported, though a much greater number UTF-8 codes can be
568 // stored and displayed. See utf.h for more information.
569 // This is a limit on the number of key codes, not on the
570 // capacity of strings.
571 gnash::key::code c = ev.keyCode();
574 // maybe _text is changed in ActionScript
575 m_cursor = std::min<size_t>(m_cursor, _text.size());
577 size_t cur_cursor = m_cursor;
578 size_t previouslinesize = 0;
579 size_t nextlinesize = 0;
580 size_t manylines = _line_starts.size();
581 LineStarts::iterator linestartit = _line_starts.begin();
582 LineStarts::const_iterator linestartend = _line_starts.end();
584 switch (c)
586 case key::BACKSPACE:
587 if (isReadOnly()) return;
588 if (m_cursor > 0)
590 s.erase(m_cursor - 1, 1);
591 m_cursor--;
592 setTextValue(s);
594 break;
596 case key::DELETEKEY:
597 if (isReadOnly()) return;
598 if (_glyphcount > m_cursor)
600 s.erase(m_cursor, 1);
601 setTextValue(s);
603 break;
605 case key::INSERT: // TODO
606 if (isReadOnly()) return;
607 break;
609 case key::HOME:
610 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
611 cur_cursor = *linestartit;
612 linestartit++;
614 m_cursor = cur_cursor;
615 break;
617 case key::PGUP:
618 // if going a page up is too far...
619 if(_scroll < _linesindisplay) {
620 _scroll = 0;
621 m_cursor = 0;
622 } else { // go a page up
623 _scroll -= _linesindisplay;
624 m_cursor = _line_starts[_scroll];
626 scrollLines();
627 break;
629 case key::UP:
630 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
631 cur_cursor = *linestartit;
632 linestartit++;
634 //if there is no previous line
635 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
636 m_cursor = 0;
637 break;
639 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
640 //if the previous line is smaller
641 if (m_cursor - cur_cursor > previouslinesize) {
642 m_cursor = *(--(--linestartit)) + previouslinesize;
643 } else {
644 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
646 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
647 --_scroll;
649 scrollLines();
650 break;
652 case key::END:
653 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
654 linestartit++;
656 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
657 break;
659 case key::PGDN:
660 //if going another page down is too far...
661 if(_scroll + _linesindisplay >= manylines) {
662 if(manylines - _linesindisplay <= 0) {
663 _scroll = 0;
664 } else {
665 _scroll = manylines - _linesindisplay;
667 if(m_cursor < _line_starts[_scroll-1]) {
668 m_cursor = _line_starts[_scroll-1];
669 } else {
670 m_cursor = _text.size();
672 } else { //go a page down
673 _scroll += _linesindisplay;
674 m_cursor = _line_starts[_scroll];
676 scrollLines();
677 break;
679 case key::DOWN:
681 while (linestartit < linestartend &&
682 *linestartit <= m_cursor ) {
683 cur_cursor = *linestartit;
684 linestartit++;
687 // linestartit should never be before _line_starts.begin()
688 const size_t currentLine = linestartit -
689 _line_starts.begin();
691 //if there is no next line
692 if (currentLine >= manylines ) {
693 m_cursor = _text.size();
694 break;
696 nextlinesize = _textRecords[currentLine].glyphs().size();
698 //if the next line is smaller
699 if (m_cursor - cur_cursor > nextlinesize) {
700 m_cursor = *linestartit + nextlinesize;
701 } else {
702 //put the cursor at the same character distance
703 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
705 if (_line_starts.size() > _linesindisplay &&
706 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
707 ++_scroll;
709 scrollLines();
710 break;
713 case key::LEFT:
714 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
715 break;
717 case key::RIGHT:
718 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
719 _glyphcount;
720 break;
722 case key::ENTER:
723 if (isReadOnly()) return;
724 if ( !multiline() )
725 break;
727 default:
729 if ( maxChars()!=0 )
731 if ( _maxChars <= _glyphcount )
733 break;
737 if (isReadOnly()) return;
738 wchar_t t = static_cast<wchar_t>(
739 gnash::key::codeMap[c][key::ASCII]);
740 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)
778 if (!visible()) return 0;
780 // Not selectable, so don't catch mouse events!
781 if (!_selectable) return 0;
783 SWFMatrix m = getMatrix(*this);
784 point p(x, y);
785 m.invert().transform(p);
787 if (_bounds.point_test(p.x, p.y)) return this;
789 return 0;
792 void
793 TextField::updateText(const std::string& str)
795 int version = getSWFVersion(*getObject(this));
796 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
797 updateText(wstr);
800 void
801 TextField::updateText(const std::wstring& wstr)
803 _textDefined = true;
805 if (_text == wstr) return;
807 set_invalidated();
809 _text = wstr;
810 format_text();
813 void
814 TextField::updateHtmlText(const std::string& str)
816 int version = getSWFVersion(*getObject(this));
817 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
818 updateHtmlText(wstr);
821 void
822 TextField::updateHtmlText(const std::wstring& wstr)
824 _htmlTextDefined = true;
826 if (_htmlText == wstr) return;
828 set_invalidated();
830 _htmlText = wstr;
831 format_text();
834 void
835 TextField::setHtmlTextValue(const std::wstring& wstr)
837 if (!doHtml()) {
838 updateText(wstr);
839 } else {
840 //updateText with no HTML tags
841 //for now, it is better to make the display correct
842 updateText(wstr);
844 updateHtmlText(wstr);
846 if ( ! _variable_name.empty() && _text_variable_registered )
848 // TODO: notify MovieClip if we have a variable name !
849 VariableRef ref = parseTextVariableRef(_variable_name);
850 as_object* tgt = ref.first;
851 if ( tgt )
853 int version = getSWFVersion(*getObject(this));
854 // we shouldn't truncate, right?
855 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
856 version));
858 else
860 // nothing to do (too early ?)
861 log_debug("setHtmlTextValue: variable name %s points to a non-existent"
862 " target, I guess we would not be registered if this was "
863 "true, or the sprite we've registered our variable name "
864 "has been unloaded", _variable_name);
869 void
870 TextField::setTextValue(const std::wstring& wstr)
872 if (!doHtml()) {
873 updateHtmlText(wstr);
874 } else {
875 //updateHtmlText and insert necessary html tags
877 updateText(wstr);
879 if ( ! _variable_name.empty() && _text_variable_registered )
881 // TODO: notify MovieClip if we have a variable name !
882 VariableRef ref = parseTextVariableRef(_variable_name);
883 as_object* tgt = ref.first;
884 if ( tgt )
886 int version = getSWFVersion(*getObject(this));
887 // we shouldn't truncate, right?
888 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
889 version));
891 else
893 // nothing to do (too early ?)
894 log_debug("setTextValue: variable name %s points to a non-existent"
895 " target, I guess we would not be registered if this was "
896 "true, or the sprite we've registered our variable name "
897 "has been unloaded", _variable_name);
902 std::string
903 TextField::get_text_value() const
905 // we need the const_cast here because registerTextVariable
906 // *might* change our text value, calling the non-const
907 // setTextValue().
908 // This happens if the TextVariable has not been already registered
909 // and during registration comes out to name an existing variable
910 // with a pre-existing value.
911 const_cast<TextField*>(this)->registerTextVariable();
913 const int version = getSWFVersion(*getObject(this));
915 return utf8::encodeCanonicalString(_text, version);
918 std::string
919 TextField::get_htmltext_value() const
921 const_cast<TextField*>(this)->registerTextVariable();
922 int version = getSWFVersion(*getObject(const_cast<TextField*>(this)));
923 return utf8::encodeCanonicalString(_htmlText, version);
926 void
927 TextField::setTextFormat(TextFormat_as& tf)
929 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
930 //This is just so we can set individual variables without having to call format_text()
931 //This calls format_text() at the end of setting TextFormat
932 if (tf.align()) setAlignment(*tf.align());
933 if (tf.size()) setFontHeight(*tf.size()); // keep twips
934 if (tf.indent()) setIndent(*tf.indent());
935 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
936 if (tf.leading()) setLeading(*tf.leading());
937 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
938 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
939 if (tf.color()) setTextColor(*tf.color());
940 if (tf.underlined()) setUnderlined(*tf.underlined());
941 if (tf.bullet()) setBullet(*tf.bullet());
942 setDisplay(tf.display());
943 if (tf.tabStops()) setTabStops(*tf.tabStops());
945 // NEED TO IMPLEMENT THESE TWO
946 if (tf.url()) setURL(*tf.url());
947 if (tf.target()) setTarget(*tf.target());
949 format_text();
952 float
953 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
956 float width = _bounds.width();
957 float right_margin = getRightMargin();
959 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
961 if (extra_space <= 0.0f) {
962 #ifdef GNASH_DEBUG_TEXTFIELDS
963 log_debug(_("TextField text doesn't fit in its boundaries: "
964 "width %g, margin %g - nothing to align"),
965 width, right_margin);
966 #endif
967 return 0.0f;
970 float shift_right = 0.0f;
972 switch (align) {
973 case ALIGN_LEFT:
974 // Nothing to do; already aligned left.
975 return 0.0f;
976 case ALIGN_CENTER:
977 // Distribute the space evenly on both sides.
978 shift_right = extra_space / 2;
979 break;
980 case ALIGN_RIGHT:
981 // Shift all the way to the right.
982 shift_right = extra_space;
983 break;
984 case ALIGN_JUSTIFY:
985 // What should we do here?
986 break;
989 // Shift the beginnings of the records on this line.
990 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
991 SWF::TextRecord& rec = _textRecords[i];
992 rec.setXOffset(rec.xOffset() + shift_right);
994 return shift_right;
997 boost::intrusive_ptr<const Font>
998 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
1000 if ( newfont == _font ) return _font;
1002 boost::intrusive_ptr<const Font> oldfont = _font;
1003 set_invalidated();
1004 _font = newfont;
1005 format_text();
1006 return oldfont;
1010 void
1011 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
1013 // tab (ASCII HT)
1014 const int space = 32;
1015 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
1016 if ( index == -1 )
1018 IF_VERBOSE_MALFORMED_SWF (
1019 log_error(_("TextField: missing glyph for space char (needed "
1020 "for TAB). Make sure DisplayObject shapes for font "
1021 "%s are being exported into your SWF file."),
1022 rec.getFont()->name());
1025 else
1027 std::vector<int> tabStops;
1028 tabStops = _tabStops;
1030 std::sort(_tabStops.begin(), _tabStops.end());
1032 int tab = 0;
1033 if ( !_tabStops.empty() )
1035 tab = _tabStops.back()+1;
1037 for (size_t i = 0; i < tabStops.size(); ++i)
1039 if (tabStops[i] > x)
1041 if((tabStops[i] - x) < tab)
1043 tab = tabStops[i] - x;
1049 // This is necessary in case the number of tabs in the text
1050 // are more than the actual number of tabStops inside the
1051 // vector
1052 if ( !(tab == _tabStops.back()+1) )
1054 SWF::TextRecord::GlyphEntry ge;
1055 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
1056 ge.advance = tab;
1057 rec.addGlyph(ge);
1058 x+=ge.advance;
1061 else
1063 SWF::TextRecord::GlyphEntry ge;
1064 ge.index = index;
1065 ge.advance = scale * rec.getFont()->get_advance(index,
1066 _embedFonts);
1068 const int tabstop = 4;
1069 rec.addGlyph(ge, tabstop);
1070 x += ge.advance * tabstop;
1075 void
1076 TextField::format_text()
1078 _textRecords.clear();
1079 _line_starts.clear();
1080 _recordStarts.clear();
1081 _glyphcount = 0;
1083 _recordStarts.push_back(0);
1085 // nothing more to do if text is empty
1086 if ( _text.empty() )
1088 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1089 // not sure we should...
1090 reset_bounding_box(0, 0);
1091 return;
1094 LineStarts::iterator linestartit = _line_starts.begin();
1095 LineStarts::const_iterator linestartend = _line_starts.end();
1097 AutoSize autoSize = getAutoSize();
1098 if (autoSize != AUTOSIZE_NONE) {
1099 // When doing WordWrap we don't want to change
1100 // the boundaries. See bug #24348
1101 if (!doWordWrap()) {
1102 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1106 // FIXME: I don't think we should query the definition
1107 // to find the appropriate font to use, as ActionScript
1108 // code should be able to change the font of a TextField
1109 if (!_font) {
1110 log_error(_("No font for TextField!"));
1111 return;
1114 boost::uint16_t fontHeight = getFontHeight();
1115 float scale = fontHeight /
1116 static_cast<float>(_font->unitsPerEM(_embedFonts));
1117 const float fontLeading = _font->leading() * scale;
1118 const boost::uint16_t leftMargin = getLeftMargin();
1119 const boost::uint16_t indent = getIndent();
1120 const boost::uint16_t blockIndent = getBlockIndent();
1121 const bool underlined = getUnderlined();
1123 /// Remember the current bounds for autosize.
1124 SWFRect oldBounds(_bounds);
1126 SWF::TextRecord rec; // one to work on
1127 rec.setFont(_font.get());
1128 rec.setUnderline(underlined);
1129 rec.setColor(getTextColor());
1130 rec.setXOffset(PADDING_TWIPS +
1131 std::max(0, leftMargin + indent + blockIndent));
1132 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1133 rec.setTextHeight(fontHeight);
1135 // create in textrecord.h
1136 rec.setURL(_url);
1137 rec.setTarget(_target);
1139 // BULLET CASE:
1141 // First, we indent 10 spaces, and then place the bullet
1142 // character (in this case, an asterisk), then we pad it
1143 // again with 10 spaces
1144 // Note: this works only for additional lines of a
1145 // bulleted list, so that is why there is a bullet format
1146 // in the beginning of format_text()
1147 if ( _bullet )
1149 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1151 SWF::TextRecord::GlyphEntry ge;
1152 ge.index = space;
1153 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1154 rec.addGlyph(ge, 5);
1156 // We use an asterisk instead of a bullet
1157 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1158 ge.index = bullet;
1159 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1160 rec.addGlyph(ge);
1162 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1163 ge.index = space;
1164 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1165 rec.addGlyph(ge, 4);
1168 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1169 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1171 // Start the bbox at the upper-left corner of the first glyph.
1172 //reset_bounding_box(x, y + fontHeight);
1174 int last_code = -1; // only used if _embedFonts
1175 int last_space_glyph = -1;
1176 size_t last_line_start_record = 0;
1178 float leading = getLeading();
1179 leading += fontLeading * scale; // not sure this is correct...
1181 _line_starts.push_back(0);
1183 // String iterators are very sensitive to
1184 // potential changes to the string (to allow for copy-on-write).
1185 // So there must be no external changes to the string or
1186 // calls to most non-const member functions during this loop.
1187 // Especially not c_str() or data().
1188 std::wstring::const_iterator it = _text.begin();
1189 const std::wstring::const_iterator e = _text.end();
1191 ///handleChar takes care of placing the glyphs
1192 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1193 last_line_start_record);
1195 // Expand bounding box to include the whole text (if autoSize and wordWrap
1196 // is not in operation.
1197 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1199 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1201 if (_autoSize == AUTOSIZE_RIGHT) {
1202 /// Autosize right expands from the previous right margin.
1203 SWFMatrix m;
1205 m.tx = oldBounds.get_x_max() - _bounds.width();
1206 m.transform(_bounds);
1208 else if (_autoSize == AUTOSIZE_CENTER) {
1209 // Autosize center expands from the previous center.
1210 SWFMatrix m;
1211 m.tx = oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1212 _bounds.width() / 2.0;
1213 m.transform(_bounds);
1217 // Add the last line to our output.
1218 _textRecords.push_back(rec);
1220 // align the last (or single) line
1221 align_line(getTextAlignment(), last_line_start_record, x);
1224 scrollLines();
1226 set_invalidated(); //redraw
1230 void
1231 TextField::scrollLines()
1233 boost::uint16_t fontHeight = getFontHeight();
1234 float scale = fontHeight /
1235 static_cast<float>(_font->unitsPerEM(_embedFonts));
1236 float fontLeading = _font->leading() * scale;
1237 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1238 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1239 size_t manylines = _line_starts.size();
1240 size_t lastvisibleline = _scroll + _linesindisplay;
1241 size_t line = 0;
1243 // If there aren't as many lines as we have scrolled, display the
1244 // end of the text.
1245 if (manylines < _scroll) {
1246 _scroll = manylines - _linesindisplay;
1247 return;
1250 // which line is the cursor on?
1251 while (line < manylines && _line_starts[line] <= m_cursor) {
1252 ++line;
1255 if (manylines - _scroll <= _linesindisplay) {
1256 // This is for if we delete a line
1257 if (manylines < _linesindisplay) _scroll = 0;
1258 else {
1259 _scroll = manylines - _linesindisplay;
1261 } else if (line < _scroll) {
1262 //if we are at a higher position, scroll the lines down
1263 _scroll -= _scroll - line;
1264 } else if (manylines > _scroll + _linesindisplay) {
1265 //if we are at a lower position, scroll the lines up
1266 if (line >= (_scroll+_linesindisplay)) {
1267 _scroll += line - (lastvisibleline);
1273 void
1274 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1275 SWF::TextRecord& rec, int& last_space_glyph,
1276 LineStarts::value_type& last_line_start_record, float div)
1278 // newline.
1279 LineStarts::iterator linestartit = _line_starts.begin();
1280 LineStarts::const_iterator linestartend = _line_starts.end();
1282 float scale = _fontHeight /
1283 static_cast<float>(_font->unitsPerEM(_embedFonts));
1284 float fontLeading = _font->leading() * scale;
1285 float leading = getLeading();
1286 leading += fontLeading * scale; // not sure this is correct...
1288 // Close out this stretch of glyphs.
1289 ++_glyphcount;
1290 _textRecords.push_back(rec);
1291 _recordStarts.push_back(_glyphcount);
1292 align_line(getTextAlignment(), last_line_start_record, x);
1294 // Expand bounding box to include last column of text ...
1295 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1296 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1299 // new paragraphs get the indent.
1300 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1301 PADDING_TWIPS;
1302 y += div * (getFontHeight() + leading);
1303 if (y >= _bounds.height()) {
1304 ++_maxScroll;
1307 // Start a new record on the next line. Other properties of the
1308 // TextRecord should be left unchanged.
1309 rec.clearGlyphs();
1310 rec.setXOffset(x);
1311 rec.setYOffset(y);
1313 last_space_glyph = -1;
1314 last_line_start_record = _textRecords.size();
1316 linestartit = _line_starts.begin();
1317 linestartend = _line_starts.end();
1318 //Fit a line_start in the correct place
1319 const size_t currentPos = _glyphcount;
1321 while (linestartit < linestartend && *linestartit < currentPos)
1323 ++linestartit;
1325 _line_starts.insert(linestartit, currentPos);
1327 // BULLET CASE:
1329 // First, we indent 10 spaces, and then place the bullet
1330 // character (in this case, an asterisk), then we pad it
1331 // again with 10 spaces
1332 // Note: this works only for additional lines of a
1333 // bulleted list, so that is why there is a bullet format
1334 // in the beginning of format_text()
1335 if (_bullet)
1337 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1338 SWF::TextRecord::GlyphEntry ge;
1339 ge.index = space;
1340 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1342 rec.addGlyph(ge,5);
1343 _glyphcount += 5;
1345 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1346 ge.index = bullet;
1347 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1348 rec.addGlyph(ge);
1349 ++_glyphcount;
1351 ge.index = space;
1352 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1354 rec.addGlyph(ge,4);
1355 _glyphcount += 4;
1359 void
1360 TextField::handleChar(std::wstring::const_iterator& it,
1361 const std::wstring::const_iterator& e, boost::int32_t& x,
1362 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1363 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1365 LineStarts::iterator linestartit = _line_starts.begin();
1366 LineStarts::const_iterator linestartend = _line_starts.end();
1368 float scale = _fontHeight /
1369 static_cast<float>(_font->unitsPerEM(_embedFonts));
1370 float fontDescent = _font->descent(_embedFonts) * scale;
1371 float fontLeading = _font->leading() * scale;
1372 float leading = getLeading();
1373 leading += fontLeading * scale; // not sure this is correct...
1375 boost::uint32_t code = 0;
1376 while (it != e)
1378 code = *it++;
1379 if (!code) break;
1381 if ( _embedFonts )
1383 x += rec.getFont()->get_kerning_adjustment(last_code,
1384 static_cast<int>(code)) * scale;
1385 last_code = static_cast<int>(code);
1388 // Expand the bounding-box to the lower-right corner of each glyph as
1389 // we generate it.
1390 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1391 switch (code)
1393 case 27:
1394 // Ignore escape
1395 break;
1396 case 9:
1397 insertTab(rec, x, scale);
1398 break;
1399 case 8:
1400 // Backspace
1402 // This is a limited hack to enable overstrike effects.
1403 // It backs the cursor up by one DisplayObject and then continues
1404 // the layout. E.g. you can use this to display an underline
1405 // cursor inside a simulated text-entry box.
1407 // ActionScript understands the '\b' escape sequence
1408 // for inserting a BS DisplayObject.
1410 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1411 // OVER NEWLINES, ETC.
1413 if (!rec.glyphs().empty())
1415 // Peek at the previous glyph, and zero out its advance
1416 // value, so the next char overwrites it.
1417 float advance = rec.glyphs().back().advance;
1418 x -= advance;
1419 // Remove one glyph
1420 rec.clearGlyphs(1);
1422 continue;
1423 case 13:
1424 case 10:
1426 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1427 break;
1429 case '<':
1430 if (doHtml())
1432 //close out this stretch of glyphs
1433 _textRecords.push_back(rec);
1434 rec.clearGlyphs();
1435 _recordStarts.push_back(_glyphcount);
1436 if (*it == '/') {
1437 while (it != e && *it != '>') {
1438 ++it;
1440 ++it;
1441 return;
1443 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1444 "gnash will just ignore the tags and "
1445 "print their content")));
1447 std::wstring discard;
1448 std::map<std::string,std::string> attributes;
1449 SWF::TextRecord newrec;
1450 newrec.setFont(rec.getFont());
1451 newrec.setUnderline(rec.underline());
1452 newrec.setColor(rec.color());
1453 newrec.setTextHeight(rec.textHeight());
1454 newrec.setXOffset(x);
1455 newrec.setYOffset(y);
1456 bool selfclosing = false;
1457 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1458 std::string s(discard.begin(), discard.end());
1459 s.assign(discard.begin(), discard.end());
1461 std::map<std::string,std::string>::const_iterator attloc;
1463 if (!complete) {
1464 //parsing went wrong
1465 continue;
1466 } else {
1467 // Don't think this is the best way to match with
1468 // tags...
1469 // TODO: assumes tags are properly nested. This isn't
1470 // correct.
1471 if (s == "U") {
1472 //underline
1473 newrec.setUnderline(true);
1474 handleChar(it, e, x, y, newrec, last_code,
1475 last_space_glyph, last_line_start_record);
1477 else if (s == "A") {
1478 // anchor (blue text).
1479 rgba color(0, 0, 0xff, 0xff);
1480 newrec.setColor(color);
1481 newrec.setUnderline(true);
1482 attloc = attributes.find("HREF");
1483 if (attloc != attributes.end()) {
1484 newrec.setURL(attloc->second);
1486 attloc = attributes.find("TARGET");
1487 if (attloc !=attributes.end()) {
1488 newrec.setTarget(attloc->second);
1490 handleChar(it, e, x, y, newrec, last_code,
1491 last_space_glyph, last_line_start_record);
1493 else if (s == "B") {
1494 //bold
1495 Font* boldfont = new Font(rec.getFont()->name(),
1496 true, rec.getFont()->isItalic());
1497 newrec.setFont(boldfont);
1498 handleChar(it, e, x, y, newrec, last_code,
1499 last_space_glyph, last_line_start_record);
1501 else if (s == "FONT") {
1502 //font
1503 boost::uint16_t originalsize = _fontHeight;
1504 attloc = attributes.find("COLOR");
1505 if (attloc != attributes.end()) {
1506 std::string hexval(attloc->second);
1507 if (hexval.empty() || hexval[0] != '#') {
1508 log_error("Unexpected color value");
1510 else {
1511 hexval.erase(0, 1);
1512 // font COLOR attribute
1513 const rgba color =
1514 colorFromHexString(hexval);
1515 newrec.setColor(color);
1518 attloc = attributes.find("FACE");
1519 if (attloc != attributes.end()) {
1520 //font FACE attribute
1521 Font* newfont = new Font(attloc->second,
1522 rec.getFont()->isBold(), rec.getFont()->isItalic());
1523 newrec.setFont(newfont);
1525 attloc = attributes.find("SIZE");
1526 if (attloc != attributes.end()) {
1527 //font SIZE attribute
1528 std::string firstchar = attloc->second.substr(0,1);
1529 if (firstchar == "+") {
1530 newrec.setTextHeight(rec.textHeight() +
1532 (pixelsToTwips(std::strtol(
1533 attloc->second.substr(1,attloc->second.length()-1).data(),
1534 NULL,10))));
1535 newrec.setYOffset(PADDING_TWIPS +
1536 newrec.textHeight() +
1537 (fontLeading - fontDescent));
1538 _fontHeight += pixelsToTwips(std::strtol(
1539 attloc->second.substr(1,attloc->second.length()-1).data(),
1540 NULL,10));
1541 } else if (firstchar == "-") {
1542 newrec.setTextHeight(rec.textHeight() -
1543 (pixelsToTwips(std::strtol(
1544 attloc->second.substr(1,attloc->second.length()-1).data(),
1545 NULL,10))));
1546 newrec.setYOffset(PADDING_TWIPS +
1547 newrec.textHeight() +
1548 (fontLeading - fontDescent));
1549 _fontHeight -= pixelsToTwips(std::strtol(
1550 attloc->second.substr(1,attloc->second.length()-1).data(),
1551 NULL,10));
1552 } else {
1553 newrec.setTextHeight(pixelsToTwips(std::strtol(
1554 attloc->second.data(), NULL, 10)));
1555 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1556 (fontLeading - fontDescent));
1557 _fontHeight = pixelsToTwips(std::strtol(
1558 attloc->second.data(), NULL, 10));
1561 handleChar(it, e, x, y, newrec, last_code,
1562 last_space_glyph, last_line_start_record);
1563 _fontHeight = originalsize;
1564 y = newrec.yOffset();
1566 else if (s == "IMG") {
1567 //image
1568 log_unimpl("<img> html tag in TextField");
1569 handleChar(it, e, x, y, newrec, last_code,
1570 last_space_glyph, last_line_start_record);
1572 else if (s == "I") {
1573 //italic
1574 Font* italicfont = new Font(rec.getFont()->name(),
1575 rec.getFont()->isBold(), true);
1576 newrec.setFont(italicfont);
1577 handleChar(it, e, x, y, newrec, last_code,
1578 last_space_glyph, last_line_start_record);
1579 } else if (s == "LI") {
1580 //list item (bullet)
1581 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1582 SWF::TextRecord::GlyphEntry ge;
1583 ge.index = space;
1584 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1585 newrec.addGlyph(ge, 5);
1587 // We use an asterisk instead of a bullet
1588 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1589 ge.index = bullet;
1590 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1591 newrec.addGlyph(ge);
1593 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1594 ge.index = space;
1595 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1596 newrec.addGlyph(ge, 4);
1598 handleChar(it, e, x, y, newrec, last_code,
1599 last_space_glyph, last_line_start_record);
1600 newLine(x, y, newrec, last_space_glyph,
1601 last_line_start_record, 1.0);
1603 else if (s == "SPAN") {
1604 //span
1605 log_unimpl("<span> html tag in TextField");
1606 handleChar(it, e, x, y, newrec, last_code,
1607 last_space_glyph, last_line_start_record);
1609 else if (s == "TEXTFORMAT") {
1610 log_debug("in textformat");
1611 //textformat
1612 boost::uint16_t originalblockindent = getBlockIndent();
1613 boost::uint16_t originalindent = getIndent();
1614 boost::uint16_t originalleading = getLeading();
1615 boost::uint16_t originalleftmargin = getLeftMargin();
1616 boost::uint16_t originalrightmargin = getRightMargin();
1617 std::vector<int> originaltabstops = getTabStops();
1618 attloc = attributes.find("BLOCKINDENT");
1619 if (attloc != attributes.end()) {
1620 //textformat BLOCKINDENT attribute
1621 setBlockIndent(pixelsToTwips(std::strtol(
1622 attloc->second.data(), NULL, 10)));
1623 if (newrec.xOffset() == std::max(0, originalleftmargin +
1624 originalindent + originalblockindent) + PADDING_TWIPS) {
1625 //if beginning of line, indent
1626 x = std::max(0, getLeftMargin() +
1627 getIndent() + getBlockIndent())
1628 + PADDING_TWIPS;
1629 newrec.setXOffset(x);
1632 attloc = attributes.find("INDENT");
1633 if (attloc != attributes.end()) {
1634 //textformat INDENT attribute
1635 setIndent(pixelsToTwips(std::strtol(
1636 attloc->second.data(), NULL, 10)));
1637 if (newrec.xOffset() == std::max(0, originalleftmargin +
1638 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1639 //if beginning of line, indent
1640 x = std::max(0, getLeftMargin() +
1641 getIndent() + getBlockIndent())
1642 + PADDING_TWIPS;
1643 newrec.setXOffset(x);
1646 attloc = attributes.find("LEADING");
1647 if (attloc != attributes.end()) {
1648 //textformat LEADING attribute
1649 setLeading(pixelsToTwips(std::strtol(
1650 attloc->second.data(), NULL, 10)));
1652 attloc = attributes.find("LEFTMARGIN");
1653 if (attloc != attributes.end()) {
1654 //textformat LEFTMARGIN attribute
1655 setLeftMargin(pixelsToTwips(std::strtol(
1656 attloc->second.data(), NULL, 10)));
1657 if (newrec.xOffset() == std::max(0, originalleftmargin +
1658 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1659 //if beginning of line, indent
1660 x = std::max(0, getLeftMargin() +
1661 getIndent() + getBlockIndent())
1662 + PADDING_TWIPS;
1663 newrec.setXOffset(x);
1666 attloc = attributes.find("RIGHTMARGIN");
1667 if (attloc != attributes.end()) {
1668 //textformat RIGHTMARGIN attribute
1669 setRightMargin(pixelsToTwips(std::strtol(
1670 attloc->second.data(), NULL, 10)));
1671 //FIXME:Should not apply this to this line if we are not at
1672 //beginning of line. Not sure how to do that.
1674 attloc = attributes.find("TABSTOPS");
1675 if (attloc != attributes.end()) {
1676 //textformat TABSTOPS attribute
1677 log_unimpl("html <textformat> tag tabstops attribute");
1679 handleChar(it, e, x, y, newrec, last_code,
1680 last_space_glyph, last_line_start_record);
1681 setBlockIndent(originalblockindent);
1682 setIndent(originalindent);
1683 setLeading(originalleading);
1684 setLeftMargin(originalleftmargin);
1685 setRightMargin(originalrightmargin);
1686 setTabStops(originaltabstops);
1688 else if (s == "P") {
1689 //paragraph
1690 if (_display == TEXTFORMAT_BLOCK) {
1691 handleChar(it, e, x, y, newrec, last_code,
1692 last_space_glyph,
1693 last_line_start_record);
1694 newLine(x, y, rec, last_space_glyph,
1695 last_line_start_record, 1.0);
1696 newLine(x, y, rec, last_space_glyph,
1697 last_line_start_record, 1.5);
1699 else {
1700 handleChar(it, e, x, y, newrec, last_code,
1701 last_space_glyph,
1702 last_line_start_record);
1705 else if (s == "BR" || s == "SBR") {
1706 //line break
1707 newLine(x, y, rec, last_space_glyph,
1708 last_line_start_record, 1.0);
1710 else {
1711 log_debug("<%s> tag is unsupported", s);
1712 if (!selfclosing) { //then recurse, look for closing tag
1713 handleChar(it, e, x, y, newrec, last_code,
1714 last_space_glyph, last_line_start_record);
1718 rec.setXOffset(x);
1719 rec.setYOffset(y);
1720 continue;
1722 // If HTML isn't enabled, carry on and insert the glyph.
1723 // FIXME: do we also want to be changing last_space_glyph?
1724 // ...because we are...
1725 case 32:
1726 last_space_glyph = rec.glyphs().size();
1727 // Don't break, as we still need to insert the space glyph.
1729 default:
1734 if ( password() )
1736 SWF::TextRecord::GlyphEntry ge;
1737 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1738 ge.index = bullet;
1739 ge.advance = scale * rec.getFont()->get_advance(bullet,
1740 _embedFonts);
1741 rec.addGlyph(ge);
1742 ++_glyphcount;
1743 break;
1745 // The font table holds up to 65535 glyphs. Casting
1746 // from uint32_t would, in the event that the code
1747 // is higher than 65535, result in the wrong DisplayObject
1748 // being chosen. Flash can currently only handle 16-bit
1749 // values.
1750 int index = rec.getFont()->get_glyph_index(
1751 static_cast<boost::uint16_t>(code), _embedFonts);
1753 IF_VERBOSE_MALFORMED_SWF (
1754 if (index == -1)
1756 // Missing glyph! Log the first few errors.
1757 static int s_log_count = 0;
1758 if (s_log_count < 10)
1760 s_log_count++;
1761 if (_embedFonts)
1763 log_swferror(_("TextField: missing embedded "
1764 "glyph for char %d. Make sure DisplayObject "
1765 "shapes for font %s are being exported "
1766 "into your SWF file"),
1767 code, _font->name());
1769 else
1771 log_swferror(_("TextField: missing device "
1772 "glyph for char %d. Maybe you don't have "
1773 "font '%s' installed in your system."),
1774 code, _font->name());
1778 // Drop through and use index == -1; this will display
1779 // using the empty-box glyph
1783 SWF::TextRecord::GlyphEntry ge;
1784 ge.index = index;
1785 ge.advance = scale * rec.getFont()->get_advance(index,
1786 _embedFonts);
1788 rec.addGlyph(ge);
1790 x += ge.advance;
1791 ++_glyphcount;
1795 float width = _bounds.width();
1796 if (x >= width - getRightMargin() - PADDING_TWIPS)
1798 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1799 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1800 getTarget(), _bounds);
1801 #endif
1803 // No wrap and no resize: truncate
1804 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1806 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1807 log_debug(" wordWrap=false, autoSize=none");
1808 #endif
1809 // Truncate long line, but keep expanding text box
1810 bool newlinefound = false;
1811 while (it != e)
1813 code = *it++;
1814 if (_embedFonts)
1816 x += rec.getFont()->get_kerning_adjustment(last_code,
1817 static_cast<int>(code)) * scale;
1818 last_code = code;
1820 // Expand the bounding-box to the lower-right corner
1821 // of each glyph, even if we don't display it
1822 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1823 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1824 log_debug("Text bbox expanded to %s (width: %f)",
1825 m_text_bounding_box, m_text_bounding_box.width());
1826 #endif
1828 if (code == 13 || code == 10)
1830 newlinefound = true;
1831 break;
1834 int index = rec.getFont()->get_glyph_index(
1835 static_cast<boost::uint16_t>(code), _embedFonts);
1836 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1839 if (!newlinefound) break;
1841 else if (doWordWrap()) {
1843 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1844 log_debug(" wordWrap=true");
1845 #endif
1847 // Insert newline if there's space or autosize != none
1849 // Close out this stretch of glyphs.
1850 _textRecords.push_back(rec);
1852 float previous_x = x;
1853 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1854 y += _fontHeight + leading;
1855 if (y >= _bounds.height()) {
1856 ++_maxScroll;
1859 // Start a new record on the next line.
1860 rec.clearGlyphs();
1861 rec.setXOffset(x);
1862 rec.setYOffset(y);
1864 // TODO : what if m_text_glyph_records is empty ?
1865 // Is it possible ?
1866 assert(!_textRecords.empty());
1867 SWF::TextRecord& last_line = _textRecords.back();
1869 linestartit = _line_starts.begin();
1870 linestartend = _line_starts.end();
1871 if (last_space_glyph == -1)
1873 // Pull the previous glyph down onto the
1874 // new line.
1875 if (!last_line.glyphs().empty())
1877 rec.addGlyph(last_line.glyphs().back());
1878 x += last_line.glyphs().back().advance;
1879 previous_x -= last_line.glyphs().back().advance;
1880 last_line.clearGlyphs(1);
1881 //record the new line start
1883 const size_t currentPos = _glyphcount;
1884 while (linestartit != linestartend &&
1885 *linestartit + 1 <= currentPos)
1887 linestartit++;
1889 _line_starts.insert(linestartit, currentPos);
1890 _recordStarts.push_back(currentPos);
1892 } else {
1893 // Move the previous word down onto the next line.
1895 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1897 const SWF::TextRecord::Glyphs::size_type lineSize =
1898 last_line.glyphs().size();
1899 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1900 ++i)
1902 rec.addGlyph(last_line.glyphs()[i]);
1903 x += last_line.glyphs()[i].advance;
1904 previous_x -= last_line.glyphs()[i].advance;
1906 last_line.clearGlyphs(lineSize - last_space_glyph);
1908 // record the position at the start of this line as
1909 // a line_start
1910 const size_t linestartpos = _glyphcount -
1911 rec.glyphs().size();
1913 while (linestartit < linestartend &&
1914 *linestartit < linestartpos)
1916 ++linestartit;
1918 _line_starts.insert(linestartit, linestartpos);
1919 _recordStarts.push_back(linestartpos);
1922 align_line(getTextAlignment(), last_line_start_record, previous_x);
1924 last_space_glyph = -1;
1925 last_line_start_record = _textRecords.size();
1928 else
1930 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1931 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1932 #endif
1939 TextField::getDefinitionVersion() const
1941 // TODO: work out if this correct.
1942 return get_root()->getDefinitionVersion();
1946 TextField::VariableRef
1947 TextField::parseTextVariableRef(const std::string& variableName) const
1949 VariableRef ret;
1950 ret.first = 0;
1952 #ifdef DEBUG_DYNTEXT_VARIABLES
1953 log_debug(_("VariableName: %s"), variableName);
1954 #endif
1956 /// Why isn't get_environment const again ?
1957 as_environment& env = const_cast<TextField*>(this)->get_environment();
1959 as_object* target = getObject(env.get_target());
1960 if ( ! target )
1962 IF_VERBOSE_MALFORMED_SWF(
1963 log_swferror(_("Current environment has no target, "
1964 "can't bind VariableName (%s) associated to "
1965 "text field. Gnash will try to register "
1966 "again on next access."), variableName);
1968 return ret;
1971 // If the variable string contains a path, we extract
1972 // the appropriate target from it and update the variable
1973 // name. We copy the string so we can assign to it if necessary.
1974 std::string parsedName = variableName;
1975 std::string path, var;
1976 if (parsePath(variableName, path, var))
1978 #ifdef DEBUG_DYNTEXT_VARIABLES
1979 log_debug(_("Variable text Path: %s, Var: %s"), path, var);
1980 #endif
1981 // find target for the path component
1982 // we use our parent's environment for this
1983 target = env.find_object(path);
1985 parsedName = var;
1988 if ( ! target )
1990 IF_VERBOSE_MALFORMED_SWF(
1991 log_swferror(_("VariableName associated to text field refers "
1992 "to an unknown target (%s). It is possible that the "
1993 "DisplayObject will be instantiated later in the SWF "
1994 "stream. Gnash will try to register again on next "
1995 "access."), path);
1997 return ret;
2000 ret.first = target;
2001 ret.second = getStringTable(*object()).find(parsedName);
2003 return ret;
2006 void
2007 TextField::registerTextVariable()
2009 //#define DEBUG_DYNTEXT_VARIABLES 1
2011 #ifdef DEBUG_DYNTEXT_VARIABLES
2012 log_debug(_("registerTextVariable() called"));
2013 #endif
2015 if (_text_variable_registered) {
2016 #ifdef DEBUG_DYNTEXT_VARIABLES
2017 log_debug(_("registerTextVariable() no-op call (already registered)"));
2018 #endif
2019 return;
2022 if (_variable_name.empty()) {
2023 #ifdef DEBUG_DYNTEXT_VARIABLES
2024 log_debug(_("string is empty, consider as registered"));
2025 #endif
2026 _text_variable_registered=true;
2027 return;
2030 VariableRef varRef = parseTextVariableRef(_variable_name);
2031 as_object* target = varRef.first;
2032 if (!target) {
2033 log_debug(_("VariableName associated to text field (%s) refer to "
2034 "an unknown target. It is possible that the DisplayObject "
2035 "will be instantiated later in the SWF stream. "
2036 "Gnash will try to register again on next access."),
2037 _variable_name);
2038 return;
2041 const string_table::key key = varRef.second;
2042 as_object* obj = getObject(this);
2043 const int version = getSWFVersion(*obj);
2044 string_table& st = getStringTable(*obj);
2046 // check if the VariableName already has a value,
2047 // in that case update text value
2048 as_value val;
2049 if (target->get_member(key, &val) ) {
2050 #ifdef DEBUG_DYNTEXT_VARIABLES
2051 log_debug(_("target object (%s @ %p) does have a member named %s"),
2052 typeName(*target), (void*)target, st.value(key));
2053 #endif
2054 // TODO: pass environment to to_string ?
2055 // as_environment& env = get_environment();
2056 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
2058 else if (_textDefined) {
2059 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
2060 #ifdef DEBUG_DYNTEXT_VARIABLES
2061 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2062 "named %s (no problem, we'll add it with value %s)"),
2063 typeName(*target), (void*)target,
2064 st.value(key), newVal);
2065 #endif
2066 target->set_member(key, newVal);
2068 else {
2069 #ifdef DEBUG_DYNTEXT_VARIABLES
2070 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2071 "named %s, and we don't have text defined"),
2072 typeName(*target), (void*)target, st.value(key));
2073 #endif
2076 MovieClip* sprite = get<MovieClip>(target);
2078 if (sprite) {
2079 // add the textfield variable to the target sprite
2080 // TODO: have set_textfield_variable take a string_table::key instead ?
2081 #ifdef DEBUG_DYNTEXT_VARIABLES
2082 log_debug("Calling set_textfield_variable(%s) against sprite %s",
2083 st.value(key), sprite->getTarget());
2084 #endif
2085 sprite->set_textfield_variable(st.value(key), this);
2088 _text_variable_registered=true;
2092 /// Parses an HTML tag (between < and >) and puts
2093 /// the contents into tag. Returns false if the
2094 /// tag was incomplete. The iterator is moved to after
2095 /// the closing tag or the end of the string.
2096 bool
2097 TextField::parseHTML(std::wstring& tag,
2098 std::map<std::string, std::string>& attributes,
2099 std::wstring::const_iterator& it,
2100 const std::wstring::const_iterator& e,
2101 bool& selfclosing) const
2103 std::string attname;
2104 std::string attvalue;
2105 while (it != e && *it != ' ') {
2106 if (*it == '/') {
2107 ++it;
2108 if (*it == '>') {
2109 ++it;
2110 selfclosing = true;
2111 return true;
2112 } else {
2113 while (it != e) {
2114 ++it;
2116 log_error("invalid html tag");
2117 return false;
2120 if (*it == '>') {
2121 ++it;
2122 return true;
2125 // Check for NULL character
2126 if (*it == 0) {
2127 log_error("found NULL character in htmlText");
2128 return false;
2130 tag.push_back(std::toupper(*it));
2131 ++it;
2133 while (it != e && *it == ' ') {
2134 ++it; //skip over spaces
2136 if (*it == '>') {
2137 ++it;
2138 return true;
2140 if (*it == '/') {
2141 ++it;
2142 if (*it == '>') {
2143 ++it;
2144 selfclosing = true;
2145 return true;
2146 } else {
2147 while (it != e) {
2148 ++it;
2150 log_error("invalid html tag");
2151 return false;
2154 //attributes
2155 while (it != e && *it != '>') {
2156 while (it != e && *it != '=' && *it != ' ') {
2158 if (*it == 0) {
2159 log_error("found NULL character in htmlText");
2160 return false;
2162 if (*it == '>') {
2163 log_error("malformed HTML tag, invalid attribute name");
2164 while (it != e) {
2165 ++it;
2167 return false;
2170 attname.push_back(std::toupper(*it));
2171 ++it;
2173 while (it != e && (*it == ' ' || *it == '=')) {
2174 ++it; //skip over spaces and '='
2176 if (it != e) {
2177 if (*it != '"') { //make sure attribute value is opened with '"'
2178 log_error("attribute value must be opened with \'\"\' "
2179 "(did you remember escape char?)");
2180 while (it != e) {
2181 ++it;
2183 return false;
2184 } else {
2185 ++it; //skip (")
2188 while (it != e && *it != '"') { //get attribute value
2190 if (*it == 0) {
2191 log_error("found NULL character in htmlText");
2192 return false;
2195 attvalue.push_back(std::toupper(*it));
2196 ++it;
2198 if (it != e) {
2199 if (*it != '"') { //make sure attribute value is closed with '"'
2200 log_error("attribute value must be closed with \'\"\' "
2201 "(did you remember escape char?)");
2202 while (it != e) {
2203 ++it;
2205 return false;
2206 } else {
2207 ++it; //skip (")
2210 attributes.insert(std::make_pair(attname, attvalue));
2211 attname = "";
2212 attvalue = "";
2213 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2214 log_error("malformed HTML tag, invalid attribute value");
2215 while (it != e) {
2216 ++it;
2218 return false;
2220 if (*it == ' ') {
2221 while (it != e && *it == ' ') {
2222 ++it; //skip over spaces
2225 if (*it == '>') {
2226 ++it;
2227 return true;
2228 } else if (*it == '/') {
2229 ++it;
2230 if (*it == '>') {
2231 ++it;
2232 selfclosing = true;
2233 return true;
2234 } else {
2235 while (it != e) {
2236 ++it;
2238 log_error("invalid html tag");
2239 return false;
2244 #ifdef GNASH_DEBUG_TEXTFIELDS
2245 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2246 #endif
2247 log_error("I declare this a HTML syntax error");
2248 return false; //since we did not return already, must be malformed...?
2251 void
2252 TextField::set_variable_name(const std::string& newname)
2254 if ( newname != _variable_name )
2256 _variable_name = newname;
2258 // The name was empty or undefined, so there's nothing more to do.
2259 if (_variable_name.empty()) return;
2261 _text_variable_registered = false;
2263 #ifdef DEBUG_DYNTEXT_VARIABLES
2264 log_debug("Calling updateText after change of variable name");
2265 #endif
2267 // Use the original definition text if this isn't dynamically
2268 // created.
2269 if (_tag) updateText(_tag->defaultText());
2271 #ifdef DEBUG_DYNTEXT_VARIABLES
2272 log_debug("Calling registerTextVariable after change of variable "
2273 "name and updateText call");
2274 #endif
2275 registerTextVariable();
2279 bool
2280 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2282 const SWFMatrix wm = getWorldMatrix(*this).invert();
2283 point lp(x, y);
2284 wm.transform(lp);
2285 return _bounds.point_test(lp.x, lp.y);
2288 bool
2289 TextField::getDrawBorder() const
2291 return _drawBorder;
2294 void
2295 TextField::setDrawBorder(bool val)
2297 if ( _drawBorder != val )
2299 set_invalidated();
2300 _drawBorder = val;
2304 rgba
2305 TextField::getBorderColor() const
2307 return _borderColor;
2310 void
2311 TextField::setBorderColor(const rgba& col)
2313 if ( _borderColor != col )
2315 set_invalidated();
2316 _borderColor = col;
2320 bool
2321 TextField::getDrawBackground() const
2323 return _drawBackground;
2326 void
2327 TextField::setDrawBackground(bool val)
2329 if ( _drawBackground != val )
2331 set_invalidated();
2332 _drawBackground = val;
2336 rgba
2337 TextField::getBackgroundColor() const
2339 return _backgroundColor;
2342 void
2343 TextField::setBackgroundColor(const rgba& col)
2345 if ( _backgroundColor != col )
2347 set_invalidated();
2348 _backgroundColor = col;
2352 void
2353 TextField::setTextColor(const rgba& col)
2355 if (_textColor != col) {
2357 set_invalidated();
2358 _textColor = col;
2359 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2360 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2364 void
2365 TextField::setEmbedFonts(bool use)
2367 if ( _embedFonts != use )
2369 set_invalidated();
2370 _embedFonts=use;
2371 format_text();
2375 void
2376 TextField::setWordWrap(bool wrap)
2378 if (_wordWrap != wrap) {
2380 set_invalidated();
2381 _wordWrap = wrap;
2382 format_text();
2386 void
2387 TextField::setLeading(boost::int16_t h)
2389 if ( _leading != h )
2391 set_invalidated();
2392 _leading = h;
2396 void
2397 TextField::setUnderlined(bool v)
2399 if ( _underlined != v )
2401 set_invalidated();
2402 _underlined = v;
2406 void
2407 TextField::setBullet(bool b)
2409 if (_bullet != b) {
2410 _bullet = b;
2414 void
2415 TextField::setTabStops(const std::vector<int>& tabStops)
2417 _tabStops.resize(tabStops.size());
2419 for (size_t i = 0; i < tabStops.size(); i ++) {
2420 _tabStops[i] = pixelsToTwips(tabStops[i]);
2423 set_invalidated();
2426 void
2427 TextField::setURL(std::string url)
2429 if ( _url != url ) {
2430 set_invalidated();
2431 _url = url;
2435 void
2436 TextField::setTarget(std::string target)
2438 if ( _target != target)
2440 set_invalidated();
2441 _target = target;
2445 void
2446 TextField::setDisplay(TextFormatDisplay display)
2448 if ( _display != display )
2450 set_invalidated();
2451 _display = display;
2455 void
2456 TextField::setAlignment(TextAlignment h)
2458 if ( _alignment != h )
2460 set_invalidated();
2461 _alignment = h;
2465 void
2466 TextField::setIndent(boost::uint16_t h)
2468 if ( _indent != h )
2470 set_invalidated();
2471 _indent = h;
2475 void
2476 TextField::setBlockIndent(boost::uint16_t h)
2478 if ( _blockIndent != h )
2480 set_invalidated();
2481 _blockIndent = h;
2485 void
2486 TextField::setRightMargin(boost::uint16_t h)
2488 if ( _rightMargin != h )
2490 set_invalidated();
2491 _rightMargin = h;
2495 void
2496 TextField::setLeftMargin(boost::uint16_t h)
2498 if (_leftMargin != h)
2500 set_invalidated();
2501 _leftMargin = h;
2505 void
2506 TextField::setFontHeight(boost::uint16_t h)
2508 if ( _fontHeight != h )
2510 set_invalidated();
2511 _fontHeight = h;
2516 TextField::TypeValue
2517 TextField::parseTypeValue(const std::string& val)
2519 StringNoCaseEqual cmp;
2521 if (cmp(val, "input")) return typeInput;
2522 if (cmp(val, "dynamic")) return typeDynamic;
2523 return typeInvalid;
2528 const char*
2529 TextField::typeValueName(TypeValue val)
2531 switch (val)
2533 case typeInput:
2534 //log_debug("typeInput returned as 'input'");
2535 return "input";
2536 case typeDynamic:
2537 //log_debug("typeDynamic returned as 'dynamic'");
2538 return "dynamic";
2539 default:
2540 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2541 return "invalid";
2546 void
2547 TextField::setAutoSize(AutoSize val)
2549 if ( val == _autoSize ) return;
2551 set_invalidated();
2553 _autoSize = val;
2554 format_text();
2557 TextField::TextAlignment
2558 TextField::getTextAlignment()
2560 TextAlignment textAlignment = getAlignment();
2562 switch (_autoSize) {
2563 case AUTOSIZE_CENTER:
2564 textAlignment = ALIGN_CENTER;
2565 break;
2566 case AUTOSIZE_LEFT:
2567 textAlignment = ALIGN_LEFT;
2568 break;
2569 case AUTOSIZE_RIGHT:
2570 textAlignment = ALIGN_RIGHT;
2571 break;
2572 default:
2573 // Leave it as it was.
2574 break;
2577 return textAlignment;
2580 void
2581 TextField::onChanged()
2583 as_object* obj = getObject(this);
2584 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2587 /// This is called by movie_root when focus is applied to this TextField.
2589 /// The return value is true if the TextField can receive focus.
2590 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2591 /// handle focus.
2592 bool
2593 TextField::handleFocus()
2596 set_invalidated();
2598 /// Select the entire text on focus.
2599 setSelection(0, _text.length());
2601 m_has_focus = true;
2603 m_cursor = _text.size();
2604 format_text();
2605 return true;
2608 /// This is called by movie_root when focus is removed from the
2609 /// current TextField.
2610 void
2611 TextField::killFocus()
2613 if ( ! m_has_focus ) return; // nothing to do
2615 set_invalidated();
2616 m_has_focus = false;
2618 format_text(); // is this needed ?
2622 void
2623 TextField::setWidth(double newwidth)
2625 const SWFRect& bounds = getBounds();
2626 _bounds.set_to_rect(bounds.get_x_min(),
2627 bounds.get_y_min(),
2628 bounds.get_x_min() + newwidth,
2629 bounds.get_y_max());
2632 void
2633 TextField::setHeight(double newheight)
2635 const SWFRect& bounds = getBounds();
2636 _bounds.set_to_rect(bounds.get_x_min(),
2637 bounds.get_y_min(),
2638 bounds.get_x_max(),
2639 bounds.get_y_min() + newheight);
2642 /// TextField interface functions
2645 } // namespace gnash
2648 // Local Variables:
2649 // mode: C++
2650 // indent-tabs-mode: t
2651 // End: