use ObjectURI more consistently
[gnash.git] / libcore / TextField.cpp
blobd9607436a6c48814c03eb0fdc039ec50b535810e
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"
51 #include "ObjectURI.h"
53 #include <algorithm>
54 #include <string>
55 #include <boost/assign/list_of.hpp>
56 #include <boost/bind.hpp>
57 #include <cstdlib>
58 #include <cctype>
59 #include <utility>
60 #include <map>
62 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
63 #define PADDING_TWIPS 40
65 // Define the following to get detailed log information about
66 // textfield bounds and HTML tags:
67 //#define GNASH_DEBUG_TEXTFIELDS 1
69 // Define this to get debugging info about text formatting
70 //#define GNASH_DEBUG_TEXT_FORMATTING 1
72 namespace gnash {
74 TextField::TextField(as_object* object, DisplayObject* parent,
75 const SWF::DefineEditTextTag& def)
77 InteractiveObject(object, parent),
78 _tag(&def),
79 _url(""),
80 _target(""),
81 _display(),
82 _tabStops(),
83 _variable_name(def.variableName()),
84 _backgroundColor(255,255,255,255),
85 _borderColor(0,0,0,255),
86 _textColor(def.color()),
87 _alignment(def.alignment()),
88 _font(0),
89 m_cursor(0u),
90 _glyphcount(0u),
91 _scroll(0u),
92 _maxScroll(1u),
93 _hScroll(0u),
94 _maxHScroll(0u),
95 _bottomScroll(0u),
96 _linesindisplay(0u),
97 _maxChars(def.maxChars()),
98 _autoSize(def.autoSize() ? AUTOSIZE_LEFT : AUTOSIZE_NONE),
99 _type(def.readOnly() ? typeDynamic : typeInput),
100 _bounds(def.bounds()),
101 _selection(0, 0),
102 _leading(def.leading()),
103 _indent(def.indent()),
104 _blockIndent(0),
105 _leftMargin(def.leftMargin()),
106 _rightMargin(def.rightMargin()),
107 _fontHeight(def.textHeight()),
108 _textDefined(def.hasText()),
109 _htmlTextDefined(def.hasText()),
110 _restrictDefined(false),
111 _underlined(false),
112 _bullet(false),
113 m_has_focus(false),
114 _multiline(def.multiline()),
115 _password(def.password()),
116 _text_variable_registered(false),
117 _drawBackground(def.border()),
118 _drawBorder(def.border()),
119 _embedFonts(def.getUseEmbeddedGlyphs()),
120 _wordWrap(def.wordWrap()),
121 _html(def.html()),
122 _selectable(!def.noSelect())
125 assert(object);
127 // WARNING! remember to set the font *before* setting text value!
128 boost::intrusive_ptr<const Font> f = def.getFont();
129 if (!f) f = fontlib::get_default_font();
130 setFont(f);
132 const int version = getSWFVersion(*object);
134 // set default text *before* calling registerTextVariable
135 // (if the textvariable already exist and has a value
136 // the text will be replaced with it)
137 if (_textDefined)
139 setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
140 setHtmlTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
143 init();
147 TextField::TextField(as_object* object, DisplayObject* parent,
148 const SWFRect& bounds)
150 InteractiveObject(object, parent),
151 _url(""),
152 _target(""),
153 _display(),
154 _tabStops(),
155 _backgroundColor(255,255,255,255),
156 _borderColor(0, 0, 0, 255),
157 _textColor(0, 0, 0, 255),
158 _alignment(ALIGN_LEFT),
159 _font(0),
160 m_cursor(0u),
161 _glyphcount(0u),
162 _scroll(0u),
163 _maxScroll(1u),
164 _hScroll(0u),
165 _maxHScroll(0u),
166 _bottomScroll(0u),
167 _linesindisplay(0u),
168 _maxChars(0),
169 _autoSize(AUTOSIZE_NONE),
170 _type(typeDynamic),
171 _bounds(bounds),
172 _selection(0, 0),
173 _leading(0),
174 _indent(0),
175 _blockIndent(0),
176 _leftMargin(0),
177 _rightMargin(0),
178 _fontHeight(12 * 20),
179 _textDefined(false),
180 _htmlTextDefined(false),
181 _restrictDefined(false),
182 _underlined(false),
183 _bullet(false),
184 m_has_focus(false),
185 _multiline(false),
186 _password(false),
187 _text_variable_registered(false),
188 _drawBackground(false),
189 _drawBorder(false),
190 _embedFonts(false),
191 _wordWrap(false),
192 _html(false),
193 _selectable(true)
195 // Use the default font (Times New Roman for Windows, Times for Mac
196 // according to docs. They don't say what it is for Linux.
197 boost::intrusive_ptr<const Font> f = fontlib::get_default_font();
198 setFont(f);
200 init();
203 void
204 TextField::init()
206 registerTextVariable();
208 reset_bounding_box(0, 0);
212 TextField::~TextField()
216 void
217 TextField::removeTextField()
219 int depth = get_depth();
220 if ( depth < 0 || depth > 1048575 )
222 //IF_VERBOSE_ASCODING_ERRORS(
223 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
224 "out of the 'dynamic' zone [0..1048575], won't remove"),
225 getTarget(), depth);
226 //);
227 return;
230 DisplayObject* p = parent();
231 assert(p); // every TextField must have a parent, right ?
233 MovieClip* parentSprite = p->to_movie();
235 if (!parentSprite) {
236 log_error("FIXME: attempt to remove a TextField being a child of a %s",
237 typeName(*p));
238 return;
241 // second argument is arbitrary, see comments above
242 // the function declaration in MovieClip.h
243 parentSprite->remove_display_object(depth, 0);
246 void
247 TextField::show_cursor(Renderer& renderer, const SWFMatrix& mat)
249 if (_textRecords.empty()) {
250 return;
252 boost::uint16_t x;
253 boost::uint16_t y;
254 boost::uint16_t h;
255 size_t i = cursorRecord();
256 SWF::TextRecord record = _textRecords[i];
258 x = record.xOffset();
259 y = record.yOffset() - record.textHeight() + getLeading();
260 h = record.textHeight();
262 if (!record.glyphs().empty()) {
263 for (unsigned int p = 0 ; p < (m_cursor - _recordStarts[i]); ++p) {
264 x += record.glyphs()[p].advance;
268 const std::vector<point> box = boost::assign::list_of
269 (point(x, y))
270 (point(x, y + h));
272 renderer.drawLine(box, rgba(0, 0, 0, 255), mat);
275 size_t
276 TextField::cursorRecord()
278 SWF::TextRecord record;
279 size_t i = 0;
281 if (_textRecords.size() != 0) {
282 while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
283 ++i;
285 return i-1;
287 return 0;
290 void
291 TextField::display(Renderer& renderer, const Transform& base)
293 const DisplayObject::MaskRenderer mr(renderer, *this);
295 registerTextVariable();
297 const bool drawBorder = getDrawBorder();
298 const bool drawBackground = getDrawBackground();
300 Transform xform = base * transform();
302 // This is a hack to handle device fonts, which are not affected by
303 // color transform.
304 if (!getEmbedFonts()) xform.colorTransform = SWFCxForm();
306 if ((drawBorder || drawBackground) && !_bounds.is_null()) {
308 std::vector<point> coords(4);
310 boost::int32_t xmin = _bounds.get_x_min();
311 boost::int32_t xmax = _bounds.get_x_max();
312 boost::int32_t ymin = _bounds.get_y_min();
313 boost::int32_t ymax = _bounds.get_y_max();
315 coords[0].setTo(xmin, ymin);
316 coords[1].setTo(xmax, ymin);
317 coords[2].setTo(xmax, ymax);
318 coords[3].setTo(xmin, ymax);
320 rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
321 rgba backgroundColor = drawBackground ? getBackgroundColor() :
322 rgba(0,0,0,0);
324 SWFCxForm cx = xform.colorTransform;
326 if (drawBorder) borderColor = cx.transform(borderColor);
328 if (drawBackground) backgroundColor = cx.transform(backgroundColor);
330 #ifdef GNASH_DEBUG_TEXTFIELDS
331 log_debug("rendering a Pol composed by corners %s", _bounds);
332 #endif
334 renderer.draw_poly(&coords.front(), 4, backgroundColor,
335 borderColor, xform.matrix, true);
339 // Draw our actual text.
340 // Using a SWFMatrix to translate to def bounds seems an hack to me.
341 // A cleaner implementation is likely correctly setting the
342 // _xOffset and _yOffset memebers in glyph records.
343 // Anyway, see bug #17954 for a testcase.
344 if (!_bounds.is_null()) {
345 xform.matrix.concatenate_translation(_bounds.get_x_min(),
346 _bounds.get_y_min());
349 _displayRecords.clear();
350 float scale = getFontHeight() /
351 static_cast<float>(_font->unitsPerEM(_embedFonts));
352 float fontLeading = _font->leading() * scale;
354 //offset the lines
355 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
356 size_t recordline;
357 for (size_t i = 0; i < _textRecords.size(); ++i) {
358 recordline = 0;
359 //find the line the record is on
360 while (recordline < _line_starts.size() &&
361 _line_starts[recordline] <= _recordStarts[i]) {
362 ++recordline;
364 //offset the line
365 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
366 //add the lines we want to the display record
367 if (_textRecords[i].yOffset() > 0 &&
368 _textRecords[i].yOffset() < _bounds.height()) {
369 _displayRecords.push_back(_textRecords[i]);
373 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
374 _embedFonts);
376 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
378 clear_invalidated();
382 void
383 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
385 if (!force && !invalidated()) return; // no need to redraw
387 ranges.add(m_old_invalidated_ranges);
389 const SWFMatrix& wm = getWorldMatrix(*this);
391 SWFRect bounds = getBounds();
392 bounds.expand_to_rect(m_text_bounding_box);
393 wm.transform(bounds);
394 ranges.add(bounds.getRange());
397 void
398 TextField::setRestrict(const std::string& restrict)
400 _restrictDefined = true;
402 std::string::const_iterator rit = restrict.begin();
403 std::string::const_iterator re = restrict.end();
404 std::set<wchar_t>::const_iterator locate;
406 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
407 for (unsigned int i = 0; i <= 255; ++i) {
408 _restrictedchars.insert(char(i));
410 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
411 _restrictedchars.clear();
414 while (rit != re) {
415 while (rit != re && *rit != '^') { //This loop allows chars
416 if (*rit == '-') {
417 log_error("invalid restrict string");
418 return;
419 } else if (*(rit+1) == '-') {
420 if (re - (rit+2) != 0) {
421 unsigned int q = *(rit+2);
422 for (unsigned int p = *rit; p <= q; (++p)){
423 _restrictedchars.insert(char(p));
425 rit += 3;
426 } else {
427 log_error("invalid restrict string");
428 return;
430 } else if (*rit == '\\') {
431 ++rit;
432 _restrictedchars.insert(*rit);
433 ++rit;
434 } else {
435 _restrictedchars.insert(*rit);
436 ++rit;
439 if (rit != re) {
440 ++rit;
442 while (rit != re && *rit != '^') { //This loop restricts chars
443 locate = _restrictedchars.find(*rit);
444 if (*rit == '-') {
445 log_error("invalid restrict string");
446 return;
447 } else if (*(rit+1) == '-') {
448 if (re - (rit+2) != 0) {
449 unsigned int q = *(rit+2);
450 for (unsigned int p = *rit; p <= q; ++p){
451 locate = _restrictedchars.find(p);
452 if(locate != _restrictedchars.end()) {
453 _restrictedchars.erase(locate);
456 ++rit;
457 ++rit;
458 ++rit;
459 } else {
460 log_error("invalid restrict string");
461 return;
463 } else if (*rit == '\\') {
464 ++rit;
465 locate = _restrictedchars.find(*rit);
466 if(locate != _restrictedchars.end()) {
467 _restrictedchars.erase(locate);
469 ++rit;
470 } else {
471 if(locate != _restrictedchars.end()) {
472 _restrictedchars.erase(locate);
474 ++rit;
477 if (rit != re) {
478 ++rit;
481 _restrict = restrict;
484 void
485 TextField::replaceSelection(const std::string& replace)
488 const int version = getSWFVersion(*getObject(this));
489 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
491 const size_t start = _selection.first;
492 const size_t replaceLength = wstr.size();
494 _text.replace(start, _selection.second - start, wstr);
495 _selection = std::make_pair(start + replaceLength, start + replaceLength);
498 void
499 TextField::setSelection(int start, int end)
502 if (_text.empty()) {
503 _selection = std::make_pair(0, 0);
504 return;
507 const size_t textLength = _text.size();
509 if (start < 0) start = 0;
510 else start = std::min<size_t>(start, textLength);
512 if (end < 0) end = 0;
513 else end = std::min<size_t>(end, textLength);
515 // The cursor position is always set to the end value, even if the
516 // two values are swapped to obtain the selection. Equal values are
517 // fine.
518 m_cursor = end;
519 if (start > end) std::swap(start, end);
521 _selection = std::make_pair(start, end);
524 void
525 TextField::notifyEvent(const event_id& ev)
527 switch (ev.id())
529 case event_id::PRESS:
531 movie_root& root = stage();
532 boost::int32_t x_mouse, y_mouse;
533 root.get_mouse_state(x_mouse, y_mouse);
535 SWFMatrix m = getMatrix(*this);
537 x_mouse -= m.get_x_translation();
538 y_mouse -= m.get_y_translation();
540 SWF::TextRecord rec;
542 for (size_t i=0; i < _textRecords.size(); ++i) {
543 if ((x_mouse > _textRecords[i].xOffset()) &&
544 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
545 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
546 (y_mouse < _textRecords[i].yOffset())) {
547 rec = _textRecords[i];
548 break;
552 if (!rec.getURL().empty()) {
553 root.getURL(rec.getURL(), rec.getTarget(), "",
554 MovieClip::METHOD_NONE);
557 break;
559 case event_id::KEY_PRESS:
561 setHtml(false); //editable html fields are not yet implemented
562 std::wstring s = _text;
564 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
565 // The maximum value is about 265, including function keys.
566 // It seems that typing in DisplayObjects outside the Latin-1 set
567 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
568 // is not supported, though a much greater number UTF-8 codes can be
569 // stored and displayed. See utf.h for more information.
570 // This is a limit on the number of key codes, not on the
571 // capacity of strings.
572 gnash::key::code c = ev.keyCode();
575 // maybe _text is changed in ActionScript
576 m_cursor = std::min<size_t>(m_cursor, _text.size());
578 size_t cur_cursor = m_cursor;
579 size_t previouslinesize = 0;
580 size_t nextlinesize = 0;
581 size_t manylines = _line_starts.size();
582 LineStarts::iterator linestartit = _line_starts.begin();
583 LineStarts::const_iterator linestartend = _line_starts.end();
585 switch (c)
587 case key::BACKSPACE:
588 if (isReadOnly()) return;
589 if (m_cursor > 0)
591 s.erase(m_cursor - 1, 1);
592 m_cursor--;
593 setTextValue(s);
595 break;
597 case key::DELETEKEY:
598 if (isReadOnly()) return;
599 if (_glyphcount > m_cursor)
601 s.erase(m_cursor, 1);
602 setTextValue(s);
604 break;
606 case key::INSERT: // TODO
607 if (isReadOnly()) return;
608 break;
610 case key::HOME:
611 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
612 cur_cursor = *linestartit;
613 linestartit++;
615 m_cursor = cur_cursor;
616 break;
618 case key::PGUP:
619 // if going a page up is too far...
620 if(_scroll < _linesindisplay) {
621 _scroll = 0;
622 m_cursor = 0;
623 } else { // go a page up
624 _scroll -= _linesindisplay;
625 m_cursor = _line_starts[_scroll];
627 scrollLines();
628 break;
630 case key::UP:
631 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
632 cur_cursor = *linestartit;
633 linestartit++;
635 //if there is no previous line
636 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
637 m_cursor = 0;
638 break;
640 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
641 //if the previous line is smaller
642 if (m_cursor - cur_cursor > previouslinesize) {
643 m_cursor = *(--(--linestartit)) + previouslinesize;
644 } else {
645 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
647 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
648 --_scroll;
650 scrollLines();
651 break;
653 case key::END:
654 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
655 linestartit++;
657 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
658 break;
660 case key::PGDN:
661 //if going another page down is too far...
662 if(_scroll + _linesindisplay >= manylines) {
663 if(manylines - _linesindisplay <= 0) {
664 _scroll = 0;
665 } else {
666 _scroll = manylines - _linesindisplay;
668 if(m_cursor < _line_starts[_scroll-1]) {
669 m_cursor = _line_starts[_scroll-1];
670 } else {
671 m_cursor = _text.size();
673 } else { //go a page down
674 _scroll += _linesindisplay;
675 m_cursor = _line_starts[_scroll];
677 scrollLines();
678 break;
680 case key::DOWN:
682 while (linestartit < linestartend &&
683 *linestartit <= m_cursor ) {
684 cur_cursor = *linestartit;
685 linestartit++;
688 // linestartit should never be before _line_starts.begin()
689 const size_t currentLine = linestartit -
690 _line_starts.begin();
692 //if there is no next line
693 if (currentLine >= manylines ) {
694 m_cursor = _text.size();
695 break;
697 nextlinesize = _textRecords[currentLine].glyphs().size();
699 //if the next line is smaller
700 if (m_cursor - cur_cursor > nextlinesize) {
701 m_cursor = *linestartit + nextlinesize;
702 } else {
703 //put the cursor at the same character distance
704 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
706 if (_line_starts.size() > _linesindisplay &&
707 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
708 ++_scroll;
710 scrollLines();
711 break;
714 case key::LEFT:
715 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
716 break;
718 case key::RIGHT:
719 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
720 _glyphcount;
721 break;
723 case key::ENTER:
724 if (isReadOnly()) return;
725 if ( !multiline() )
726 break;
728 default:
730 if ( maxChars()!=0 )
732 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)
744 if (!_restrictDefined) {
745 // Insert one copy of the character
746 // at the cursor position.
747 s.insert(m_cursor, 1, t);
748 m_cursor++;
749 } else if (_restrictedchars.count(t)) {
750 // Insert one copy of the character
751 // at the cursor position.
752 s.insert(m_cursor, 1, t);
753 m_cursor++;
754 } else if (_restrictedchars.count(tolower(t))) {
755 // restrict substitutes the opposite case
756 s.insert(m_cursor, 1, tolower(t));
757 m_cursor++;
758 } else if (_restrictedchars.count(toupper(t))) {
759 // restrict substitutes the opposite case
760 s.insert(m_cursor, 1, toupper(t));
761 m_cursor++;
764 setTextValue(s);
766 onChanged();
767 set_invalidated();
770 default:
771 return;
775 InteractiveObject*
776 TextField::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
779 if (!visible()) return 0;
781 // Not selectable, so don't catch mouse events!
782 if (!_selectable) return 0;
784 SWFMatrix m = getMatrix(*this);
785 point p(x, y);
786 m.invert().transform(p);
788 if (_bounds.point_test(p.x, p.y)) return this;
790 return 0;
793 void
794 TextField::updateText(const std::string& str)
796 const int version = getSWFVersion(*getObject(this));
797 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
798 updateText(wstr);
801 void
802 TextField::updateText(const std::wstring& wstr)
804 _textDefined = true;
806 if (_text == wstr) return;
808 set_invalidated();
810 _text = wstr;
811 format_text();
814 void
815 TextField::updateHtmlText(const std::string& str)
817 int version = getSWFVersion(*getObject(this));
818 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
819 updateHtmlText(wstr);
822 void
823 TextField::updateHtmlText(const std::wstring& wstr)
825 _htmlTextDefined = true;
827 if (_htmlText == wstr) return;
829 set_invalidated();
831 _htmlText = wstr;
832 format_text();
835 void
836 TextField::setHtmlTextValue(const std::wstring& wstr)
838 if (!doHtml()) {
839 updateText(wstr);
840 } else {
841 //updateText with no HTML tags
842 //for now, it is better to make the display correct
843 updateText(wstr);
845 updateHtmlText(wstr);
847 if ( ! _variable_name.empty() && _text_variable_registered )
849 // TODO: notify MovieClip if we have a variable name !
850 VariableRef ref = parseTextVariableRef(_variable_name);
851 as_object* tgt = ref.first;
852 if ( tgt )
854 const int version = getSWFVersion(*getObject(this));
855 // we shouldn't truncate, right?
856 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
857 version));
859 else
861 // nothing to do (too early ?)
862 log_debug("setHtmlTextValue: variable name %s points to a non-existent"
863 " target, I guess we would not be registered if this was "
864 "true, or the sprite we've registered our variable name "
865 "has been unloaded", _variable_name);
870 void
871 TextField::setTextValue(const std::wstring& wstr)
873 if (!doHtml()) {
874 updateHtmlText(wstr);
875 } else {
876 //updateHtmlText and insert necessary html tags
878 updateText(wstr);
880 if ( ! _variable_name.empty() && _text_variable_registered )
882 // TODO: notify MovieClip if we have a variable name !
883 VariableRef ref = parseTextVariableRef(_variable_name);
884 as_object* tgt = ref.first;
885 if ( tgt )
887 const int version = getSWFVersion(*getObject(this));
888 // we shouldn't truncate, right?
889 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
890 version));
892 else
894 // nothing to do (too early ?)
895 log_debug("setTextValue: variable name %s points to a non-existent"
896 " target, I guess we would not be registered if this was "
897 "true, or the sprite we've registered our variable name "
898 "has been unloaded", _variable_name);
903 std::string
904 TextField::get_text_value() const
906 // we need the const_cast here because registerTextVariable
907 // *might* change our text value, calling the non-const
908 // setTextValue().
909 // This happens if the TextVariable has not been already registered
910 // and during registration comes out to name an existing variable
911 // with a pre-existing value.
912 const_cast<TextField*>(this)->registerTextVariable();
914 const int version = getSWFVersion(*getObject(this));
916 return utf8::encodeCanonicalString(_text, version);
919 std::string
920 TextField::get_htmltext_value() const
922 const_cast<TextField*>(this)->registerTextVariable();
923 const int version = getSWFVersion(*getObject(const_cast<TextField*>(this)));
924 return utf8::encodeCanonicalString(_htmlText, version);
927 void
928 TextField::setTextFormat(TextFormat_as& tf)
930 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
931 //This is just so we can set individual variables without having to call format_text()
932 //This calls format_text() at the end of setting TextFormat
933 if (tf.align()) setAlignment(*tf.align());
934 if (tf.size()) setFontHeight(*tf.size()); // keep twips
935 if (tf.indent()) setIndent(*tf.indent());
936 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
937 if (tf.leading()) setLeading(*tf.leading());
938 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
939 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
940 if (tf.color()) setTextColor(*tf.color());
941 if (tf.underlined()) setUnderlined(*tf.underlined());
942 if (tf.bullet()) setBullet(*tf.bullet());
943 setDisplay(tf.display());
944 if (tf.tabStops()) setTabStops(*tf.tabStops());
946 // NEED TO IMPLEMENT THESE TWO
947 if (tf.url()) setURL(*tf.url());
948 if (tf.target()) setTarget(*tf.target());
950 format_text();
953 float
954 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
957 float width = _bounds.width();
958 float right_margin = getRightMargin();
960 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
962 if (extra_space <= 0.0f) {
963 #ifdef GNASH_DEBUG_TEXTFIELDS
964 log_debug(_("TextField text doesn't fit in its boundaries: "
965 "width %g, margin %g - nothing to align"),
966 width, right_margin);
967 #endif
968 return 0.0f;
971 float shift_right = 0.0f;
973 switch (align) {
974 case ALIGN_LEFT:
975 // Nothing to do; already aligned left.
976 return 0.0f;
977 case ALIGN_CENTER:
978 // Distribute the space evenly on both sides.
979 shift_right = extra_space / 2;
980 break;
981 case ALIGN_RIGHT:
982 // Shift all the way to the right.
983 shift_right = extra_space;
984 break;
985 case ALIGN_JUSTIFY:
986 // What should we do here?
987 break;
990 // Shift the beginnings of the records on this line.
991 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
992 SWF::TextRecord& rec = _textRecords[i];
993 rec.setXOffset(rec.xOffset() + shift_right);
995 return shift_right;
998 boost::intrusive_ptr<const Font>
999 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
1001 if ( newfont == _font ) return _font;
1003 boost::intrusive_ptr<const Font> oldfont = _font;
1004 set_invalidated();
1005 _font = newfont;
1006 format_text();
1007 return oldfont;
1011 void
1012 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
1014 // tab (ASCII HT)
1015 const int space = 32;
1016 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
1017 if ( index == -1 )
1019 IF_VERBOSE_MALFORMED_SWF (
1020 log_error(_("TextField: missing glyph for space char (needed "
1021 "for TAB). Make sure DisplayObject shapes for font "
1022 "%s are being exported into your SWF file."),
1023 rec.getFont()->name());
1026 else
1028 std::vector<int> tabStops;
1029 tabStops = _tabStops;
1031 std::sort(_tabStops.begin(), _tabStops.end());
1033 int tab = 0;
1034 if ( !_tabStops.empty() )
1036 tab = _tabStops.back()+1;
1038 for (size_t i = 0; i < tabStops.size(); ++i)
1040 if (tabStops[i] > x)
1042 if((tabStops[i] - x) < tab)
1044 tab = tabStops[i] - x;
1050 // This is necessary in case the number of tabs in the text
1051 // are more than the actual number of tabStops inside the
1052 // vector
1053 if ( !(tab == _tabStops.back()+1) )
1055 SWF::TextRecord::GlyphEntry ge;
1056 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
1057 ge.advance = tab;
1058 rec.addGlyph(ge);
1059 x+=ge.advance;
1062 else
1064 SWF::TextRecord::GlyphEntry ge;
1065 ge.index = index;
1066 ge.advance = scale * rec.getFont()->get_advance(index,
1067 _embedFonts);
1069 const int tabstop = 4;
1070 rec.addGlyph(ge, tabstop);
1071 x += ge.advance * tabstop;
1076 void
1077 TextField::format_text()
1079 _textRecords.clear();
1080 _line_starts.clear();
1081 _recordStarts.clear();
1082 _glyphcount = 0;
1084 _recordStarts.push_back(0);
1086 // nothing more to do if text is empty
1087 if ( _text.empty() )
1089 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1090 // not sure we should...
1091 reset_bounding_box(0, 0);
1092 return;
1095 LineStarts::iterator linestartit = _line_starts.begin();
1096 LineStarts::const_iterator linestartend = _line_starts.end();
1098 AutoSize autoSize = getAutoSize();
1099 if (autoSize != AUTOSIZE_NONE) {
1100 // When doing WordWrap we don't want to change
1101 // the boundaries. See bug #24348
1102 if (!doWordWrap()) {
1103 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1107 // FIXME: I don't think we should query the definition
1108 // to find the appropriate font to use, as ActionScript
1109 // code should be able to change the font of a TextField
1110 if (!_font) {
1111 log_error(_("No font for TextField!"));
1112 return;
1115 boost::uint16_t fontHeight = getFontHeight();
1116 float scale = fontHeight /
1117 static_cast<float>(_font->unitsPerEM(_embedFonts));
1118 const float fontLeading = _font->leading() * scale;
1119 const boost::uint16_t leftMargin = getLeftMargin();
1120 const boost::uint16_t indent = getIndent();
1121 const boost::uint16_t blockIndent = getBlockIndent();
1122 const bool underlined = getUnderlined();
1124 /// Remember the current bounds for autosize.
1125 SWFRect oldBounds(_bounds);
1127 SWF::TextRecord rec; // one to work on
1128 rec.setFont(_font.get());
1129 rec.setUnderline(underlined);
1130 rec.setColor(getTextColor());
1131 rec.setXOffset(PADDING_TWIPS +
1132 std::max(0, leftMargin + indent + blockIndent));
1133 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1134 rec.setTextHeight(fontHeight);
1136 // create in textrecord.h
1137 rec.setURL(_url);
1138 rec.setTarget(_target);
1140 // BULLET CASE:
1142 // First, we indent 10 spaces, and then place the bullet
1143 // character (in this case, an asterisk), then we pad it
1144 // again with 10 spaces
1145 // Note: this works only for additional lines of a
1146 // bulleted list, so that is why there is a bullet format
1147 // in the beginning of format_text()
1148 if ( _bullet )
1150 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1152 SWF::TextRecord::GlyphEntry ge;
1153 ge.index = space;
1154 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1155 rec.addGlyph(ge, 5);
1157 // We use an asterisk instead of a bullet
1158 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1159 ge.index = bullet;
1160 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1161 rec.addGlyph(ge);
1163 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1164 ge.index = space;
1165 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1166 rec.addGlyph(ge, 4);
1169 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1170 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1172 // Start the bbox at the upper-left corner of the first glyph.
1173 //reset_bounding_box(x, y + fontHeight);
1175 int last_code = -1; // only used if _embedFonts
1176 int last_space_glyph = -1;
1177 size_t last_line_start_record = 0;
1179 float leading = getLeading();
1180 leading += fontLeading * scale; // not sure this is correct...
1182 _line_starts.push_back(0);
1184 // String iterators are very sensitive to
1185 // potential changes to the string (to allow for copy-on-write).
1186 // So there must be no external changes to the string or
1187 // calls to most non-const member functions during this loop.
1188 // Especially not c_str() or data().
1189 std::wstring::const_iterator it = _text.begin();
1190 const std::wstring::const_iterator e = _text.end();
1192 ///handleChar takes care of placing the glyphs
1193 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1194 last_line_start_record);
1196 // Expand bounding box to include the whole text (if autoSize and wordWrap
1197 // is not in operation.
1198 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1200 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1202 if (_autoSize == AUTOSIZE_RIGHT) {
1203 /// Autosize right expands from the previous right margin.
1204 SWFMatrix m;
1206 m.tx = oldBounds.get_x_max() - _bounds.width();
1207 m.transform(_bounds);
1209 else if (_autoSize == AUTOSIZE_CENTER) {
1210 // Autosize center expands from the previous center.
1211 SWFMatrix m;
1212 m.tx = oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1213 _bounds.width() / 2.0;
1214 m.transform(_bounds);
1218 // Add the last line to our output.
1219 _textRecords.push_back(rec);
1221 // align the last (or single) line
1222 align_line(getTextAlignment(), last_line_start_record, x);
1225 scrollLines();
1227 set_invalidated(); //redraw
1231 void
1232 TextField::scrollLines()
1234 boost::uint16_t fontHeight = getFontHeight();
1235 float scale = fontHeight /
1236 static_cast<float>(_font->unitsPerEM(_embedFonts));
1237 float fontLeading = _font->leading() * scale;
1238 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1239 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1240 size_t manylines = _line_starts.size();
1241 size_t lastvisibleline = _scroll + _linesindisplay;
1242 size_t line = 0;
1244 // If there aren't as many lines as we have scrolled, display the
1245 // end of the text.
1246 if (manylines < _scroll) {
1247 _scroll = manylines - _linesindisplay;
1248 return;
1251 // which line is the cursor on?
1252 while (line < manylines && _line_starts[line] <= m_cursor) {
1253 ++line;
1256 if (manylines - _scroll <= _linesindisplay) {
1257 // This is for if we delete a line
1258 if (manylines < _linesindisplay) _scroll = 0;
1259 else {
1260 _scroll = manylines - _linesindisplay;
1262 } else if (line < _scroll) {
1263 //if we are at a higher position, scroll the lines down
1264 _scroll -= _scroll - line;
1265 } else if (manylines > _scroll + _linesindisplay) {
1266 //if we are at a lower position, scroll the lines up
1267 if (line >= (_scroll+_linesindisplay)) {
1268 _scroll += line - (lastvisibleline);
1274 void
1275 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1276 SWF::TextRecord& rec, int& last_space_glyph,
1277 LineStarts::value_type& last_line_start_record, float div)
1279 // newline.
1280 LineStarts::iterator linestartit = _line_starts.begin();
1281 LineStarts::const_iterator linestartend = _line_starts.end();
1283 float scale = _fontHeight /
1284 static_cast<float>(_font->unitsPerEM(_embedFonts));
1285 float fontLeading = _font->leading() * scale;
1286 float leading = getLeading();
1287 leading += fontLeading * scale; // not sure this is correct...
1289 // Close out this stretch of glyphs.
1290 ++_glyphcount;
1291 _textRecords.push_back(rec);
1292 _recordStarts.push_back(_glyphcount);
1293 align_line(getTextAlignment(), last_line_start_record, x);
1295 // Expand bounding box to include last column of text ...
1296 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1297 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1300 // new paragraphs get the indent.
1301 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1302 PADDING_TWIPS;
1303 y += div * (getFontHeight() + leading);
1304 if (y >= _bounds.height()) {
1305 ++_maxScroll;
1308 // Start a new record on the next line. Other properties of the
1309 // TextRecord should be left unchanged.
1310 rec.clearGlyphs();
1311 rec.setXOffset(x);
1312 rec.setYOffset(y);
1314 last_space_glyph = -1;
1315 last_line_start_record = _textRecords.size();
1317 linestartit = _line_starts.begin();
1318 linestartend = _line_starts.end();
1319 //Fit a line_start in the correct place
1320 const size_t currentPos = _glyphcount;
1322 while (linestartit < linestartend && *linestartit < currentPos)
1324 ++linestartit;
1326 _line_starts.insert(linestartit, currentPos);
1328 // BULLET CASE:
1330 // First, we indent 10 spaces, and then place the bullet
1331 // character (in this case, an asterisk), then we pad it
1332 // again with 10 spaces
1333 // Note: this works only for additional lines of a
1334 // bulleted list, so that is why there is a bullet format
1335 // in the beginning of format_text()
1336 if (_bullet)
1338 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1339 SWF::TextRecord::GlyphEntry ge;
1340 ge.index = space;
1341 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1343 rec.addGlyph(ge,5);
1344 _glyphcount += 5;
1346 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1347 ge.index = bullet;
1348 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1349 rec.addGlyph(ge);
1350 ++_glyphcount;
1352 ge.index = space;
1353 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1355 rec.addGlyph(ge,4);
1356 _glyphcount += 4;
1360 void
1361 TextField::handleChar(std::wstring::const_iterator& it,
1362 const std::wstring::const_iterator& e, boost::int32_t& x,
1363 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1364 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1366 LineStarts::iterator linestartit = _line_starts.begin();
1367 LineStarts::const_iterator linestartend = _line_starts.end();
1369 float scale = _fontHeight /
1370 static_cast<float>(_font->unitsPerEM(_embedFonts));
1371 float fontDescent = _font->descent(_embedFonts) * scale;
1372 float fontLeading = _font->leading() * scale;
1373 float leading = getLeading();
1374 leading += fontLeading * scale; // not sure this is correct...
1376 boost::uint32_t code = 0;
1377 while (it != e)
1379 code = *it++;
1380 if (!code) break;
1382 if ( _embedFonts )
1384 x += rec.getFont()->get_kerning_adjustment(last_code,
1385 static_cast<int>(code)) * scale;
1386 last_code = static_cast<int>(code);
1389 // Expand the bounding-box to the lower-right corner of each glyph as
1390 // we generate it.
1391 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1392 switch (code)
1394 case 27:
1395 // Ignore escape
1396 break;
1397 case 9:
1398 insertTab(rec, x, scale);
1399 break;
1400 case 8:
1401 // Backspace
1403 // This is a limited hack to enable overstrike effects.
1404 // It backs the cursor up by one DisplayObject and then continues
1405 // the layout. E.g. you can use this to display an underline
1406 // cursor inside a simulated text-entry box.
1408 // ActionScript understands the '\b' escape sequence
1409 // for inserting a BS DisplayObject.
1411 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1412 // OVER NEWLINES, ETC.
1414 if (!rec.glyphs().empty())
1416 // Peek at the previous glyph, and zero out its advance
1417 // value, so the next char overwrites it.
1418 float advance = rec.glyphs().back().advance;
1419 x -= advance;
1420 // Remove one glyph
1421 rec.clearGlyphs(1);
1423 continue;
1424 case 13:
1425 case 10:
1427 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1428 break;
1430 case '<':
1431 if (doHtml())
1433 //close out this stretch of glyphs
1434 _textRecords.push_back(rec);
1435 rec.clearGlyphs();
1436 _recordStarts.push_back(_glyphcount);
1437 if (*it == '/') {
1438 while (it != e && *it != '>') {
1439 ++it;
1441 ++it;
1442 return;
1444 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1445 "gnash will just ignore the tags and "
1446 "print their content")));
1448 std::wstring discard;
1449 std::map<std::string,std::string> attributes;
1450 SWF::TextRecord newrec;
1451 newrec.setFont(rec.getFont());
1452 newrec.setUnderline(rec.underline());
1453 newrec.setColor(rec.color());
1454 newrec.setTextHeight(rec.textHeight());
1455 newrec.setXOffset(x);
1456 newrec.setYOffset(y);
1457 bool selfclosing = false;
1458 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1459 std::string s(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 // FIXME: should this be a log_aserror
1509 // or log_unimpl ? It is triggered
1510 // by TextFieldHTML.as
1511 log_error("Unexpected value '%s' in "
1512 "TextField font color attribute",
1513 hexval);
1515 else {
1516 hexval.erase(0, 1);
1517 // font COLOR attribute
1518 const rgba color =
1519 colorFromHexString(hexval);
1520 newrec.setColor(color);
1523 attloc = attributes.find("FACE");
1524 if (attloc != attributes.end()) {
1525 //font FACE attribute
1526 Font* newfont = new Font(attloc->second,
1527 rec.getFont()->isBold(), rec.getFont()->isItalic());
1528 newrec.setFont(newfont);
1530 attloc = attributes.find("SIZE");
1531 if (attloc != attributes.end()) {
1532 //font SIZE attribute
1533 std::string firstchar = attloc->second.substr(0,1);
1534 if (firstchar == "+") {
1535 newrec.setTextHeight(rec.textHeight() +
1537 (pixelsToTwips(std::strtol(
1538 attloc->second.substr(1,attloc->second.length()-1).data(),
1539 NULL,10))));
1540 newrec.setYOffset(PADDING_TWIPS +
1541 newrec.textHeight() +
1542 (fontLeading - fontDescent));
1543 _fontHeight += pixelsToTwips(std::strtol(
1544 attloc->second.substr(1,attloc->second.length()-1).data(),
1545 NULL,10));
1546 } else if (firstchar == "-") {
1547 newrec.setTextHeight(rec.textHeight() -
1548 (pixelsToTwips(std::strtol(
1549 attloc->second.substr(1,attloc->second.length()-1).data(),
1550 NULL,10))));
1551 newrec.setYOffset(PADDING_TWIPS +
1552 newrec.textHeight() +
1553 (fontLeading - fontDescent));
1554 _fontHeight -= pixelsToTwips(std::strtol(
1555 attloc->second.substr(1,attloc->second.length()-1).data(),
1556 NULL,10));
1557 } else {
1558 newrec.setTextHeight(pixelsToTwips(std::strtol(
1559 attloc->second.data(), NULL, 10)));
1560 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1561 (fontLeading - fontDescent));
1562 _fontHeight = pixelsToTwips(std::strtol(
1563 attloc->second.data(), NULL, 10));
1566 handleChar(it, e, x, y, newrec, last_code,
1567 last_space_glyph, last_line_start_record);
1568 _fontHeight = originalsize;
1569 y = newrec.yOffset();
1571 else if (s == "IMG") {
1572 //image
1573 log_unimpl("<img> html tag in TextField");
1574 handleChar(it, e, x, y, newrec, last_code,
1575 last_space_glyph, last_line_start_record);
1577 else if (s == "I") {
1578 //italic
1579 Font* italicfont = new Font(rec.getFont()->name(),
1580 rec.getFont()->isBold(), true);
1581 newrec.setFont(italicfont);
1582 handleChar(it, e, x, y, newrec, last_code,
1583 last_space_glyph, last_line_start_record);
1584 } else if (s == "LI") {
1585 //list item (bullet)
1586 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1587 SWF::TextRecord::GlyphEntry ge;
1588 ge.index = space;
1589 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1590 newrec.addGlyph(ge, 5);
1592 // We use an asterisk instead of a bullet
1593 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1594 ge.index = bullet;
1595 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1596 newrec.addGlyph(ge);
1598 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1599 ge.index = space;
1600 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1601 newrec.addGlyph(ge, 4);
1603 handleChar(it, e, x, y, newrec, last_code,
1604 last_space_glyph, last_line_start_record);
1605 newLine(x, y, newrec, last_space_glyph,
1606 last_line_start_record, 1.0);
1608 else if (s == "SPAN") {
1609 //span
1610 log_unimpl("<span> html tag in TextField");
1611 handleChar(it, e, x, y, newrec, last_code,
1612 last_space_glyph, last_line_start_record);
1614 else if (s == "TEXTFORMAT") {
1615 log_debug("in textformat");
1616 //textformat
1617 boost::uint16_t originalblockindent = getBlockIndent();
1618 boost::uint16_t originalindent = getIndent();
1619 boost::uint16_t originalleading = getLeading();
1620 boost::uint16_t originalleftmargin = getLeftMargin();
1621 boost::uint16_t originalrightmargin = getRightMargin();
1622 std::vector<int> originaltabstops = getTabStops();
1623 attloc = attributes.find("BLOCKINDENT");
1624 if (attloc != attributes.end()) {
1625 //textformat BLOCKINDENT attribute
1626 setBlockIndent(pixelsToTwips(std::strtol(
1627 attloc->second.data(), NULL, 10)));
1628 if (newrec.xOffset() == std::max(0, originalleftmargin +
1629 originalindent + originalblockindent) + PADDING_TWIPS) {
1630 //if beginning of line, indent
1631 x = std::max(0, getLeftMargin() +
1632 getIndent() + getBlockIndent())
1633 + PADDING_TWIPS;
1634 newrec.setXOffset(x);
1637 attloc = attributes.find("INDENT");
1638 if (attloc != attributes.end()) {
1639 //textformat INDENT attribute
1640 setIndent(pixelsToTwips(std::strtol(
1641 attloc->second.data(), NULL, 10)));
1642 if (newrec.xOffset() == std::max(0, originalleftmargin +
1643 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1644 //if beginning of line, indent
1645 x = std::max(0, getLeftMargin() +
1646 getIndent() + getBlockIndent())
1647 + PADDING_TWIPS;
1648 newrec.setXOffset(x);
1651 attloc = attributes.find("LEADING");
1652 if (attloc != attributes.end()) {
1653 //textformat LEADING attribute
1654 setLeading(pixelsToTwips(std::strtol(
1655 attloc->second.data(), NULL, 10)));
1657 attloc = attributes.find("LEFTMARGIN");
1658 if (attloc != attributes.end()) {
1659 //textformat LEFTMARGIN attribute
1660 setLeftMargin(pixelsToTwips(std::strtol(
1661 attloc->second.data(), NULL, 10)));
1662 if (newrec.xOffset() == std::max(0, originalleftmargin +
1663 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1664 //if beginning of line, indent
1665 x = std::max(0, getLeftMargin() +
1666 getIndent() + getBlockIndent())
1667 + PADDING_TWIPS;
1668 newrec.setXOffset(x);
1671 attloc = attributes.find("RIGHTMARGIN");
1672 if (attloc != attributes.end()) {
1673 //textformat RIGHTMARGIN attribute
1674 setRightMargin(pixelsToTwips(std::strtol(
1675 attloc->second.data(), NULL, 10)));
1676 //FIXME:Should not apply this to this line if we are not at
1677 //beginning of line. Not sure how to do that.
1679 attloc = attributes.find("TABSTOPS");
1680 if (attloc != attributes.end()) {
1681 //textformat TABSTOPS attribute
1682 log_unimpl("html <textformat> tag tabstops attribute");
1684 handleChar(it, e, x, y, newrec, last_code,
1685 last_space_glyph, last_line_start_record);
1686 setBlockIndent(originalblockindent);
1687 setIndent(originalindent);
1688 setLeading(originalleading);
1689 setLeftMargin(originalleftmargin);
1690 setRightMargin(originalrightmargin);
1691 setTabStops(originaltabstops);
1693 else if (s == "P") {
1694 //paragraph
1695 if (_display == TEXTFORMAT_BLOCK) {
1696 handleChar(it, e, x, y, newrec, last_code,
1697 last_space_glyph,
1698 last_line_start_record);
1699 newLine(x, y, rec, last_space_glyph,
1700 last_line_start_record, 1.0);
1701 newLine(x, y, rec, last_space_glyph,
1702 last_line_start_record, 1.5);
1704 else {
1705 handleChar(it, e, x, y, newrec, last_code,
1706 last_space_glyph,
1707 last_line_start_record);
1710 else if (s == "BR" || s == "SBR") {
1711 //line break
1712 newLine(x, y, rec, last_space_glyph,
1713 last_line_start_record, 1.0);
1715 else {
1716 log_debug("<%s> tag is unsupported", s);
1717 if (!selfclosing) { //then recurse, look for closing tag
1718 handleChar(it, e, x, y, newrec, last_code,
1719 last_space_glyph, last_line_start_record);
1723 rec.setXOffset(x);
1724 rec.setYOffset(y);
1725 continue;
1727 // If HTML isn't enabled, carry on and insert the glyph.
1728 // FIXME: do we also want to be changing last_space_glyph?
1729 // ...because we are...
1730 case 32:
1731 last_space_glyph = rec.glyphs().size();
1732 // Don't break, as we still need to insert the space glyph.
1734 default:
1739 if ( password() )
1741 SWF::TextRecord::GlyphEntry ge;
1742 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1743 ge.index = bullet;
1744 ge.advance = scale * rec.getFont()->get_advance(bullet,
1745 _embedFonts);
1746 rec.addGlyph(ge);
1747 ++_glyphcount;
1748 break;
1750 // The font table holds up to 65535 glyphs. Casting
1751 // from uint32_t would, in the event that the code
1752 // is higher than 65535, result in the wrong DisplayObject
1753 // being chosen. Flash can currently only handle 16-bit
1754 // values.
1755 int index = rec.getFont()->get_glyph_index(
1756 static_cast<boost::uint16_t>(code), _embedFonts);
1758 IF_VERBOSE_MALFORMED_SWF (
1759 if (index == -1)
1761 // Missing glyph! Log the first few errors.
1762 static int s_log_count = 0;
1763 if (s_log_count < 10)
1765 s_log_count++;
1766 if (_embedFonts)
1768 log_swferror(_("TextField: missing embedded "
1769 "glyph for char %d. Make sure DisplayObject "
1770 "shapes for font %s are being exported "
1771 "into your SWF file"),
1772 code, _font->name());
1774 else
1776 log_swferror(_("TextField: missing device "
1777 "glyph for char %d. Maybe you don't have "
1778 "font '%s' installed in your system."),
1779 code, _font->name());
1783 // Drop through and use index == -1; this will display
1784 // using the empty-box glyph
1788 SWF::TextRecord::GlyphEntry ge;
1789 ge.index = index;
1790 ge.advance = scale * rec.getFont()->get_advance(index,
1791 _embedFonts);
1793 rec.addGlyph(ge);
1795 x += ge.advance;
1796 ++_glyphcount;
1800 float width = _bounds.width();
1801 if (x >= width - getRightMargin() - PADDING_TWIPS)
1803 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1804 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1805 getTarget(), _bounds);
1806 #endif
1808 // No wrap and no resize: truncate
1809 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1811 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1812 log_debug(" wordWrap=false, autoSize=none");
1813 #endif
1814 // Truncate long line, but keep expanding text box
1815 bool newlinefound = false;
1816 while (it != e)
1818 code = *it++;
1819 if (_embedFonts)
1821 x += rec.getFont()->get_kerning_adjustment(last_code,
1822 static_cast<int>(code)) * scale;
1823 last_code = code;
1825 // Expand the bounding-box to the lower-right corner
1826 // of each glyph, even if we don't display it
1827 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1828 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1829 log_debug("Text bbox expanded to %s (width: %f)",
1830 m_text_bounding_box, m_text_bounding_box.width());
1831 #endif
1833 if (code == 13 || code == 10)
1835 newlinefound = true;
1836 break;
1839 int index = rec.getFont()->get_glyph_index(
1840 static_cast<boost::uint16_t>(code), _embedFonts);
1841 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1844 if (!newlinefound) break;
1846 else if (doWordWrap()) {
1848 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1849 log_debug(" wordWrap=true");
1850 #endif
1852 // Insert newline if there's space or autosize != none
1854 // Close out this stretch of glyphs.
1855 _textRecords.push_back(rec);
1857 float previous_x = x;
1858 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1859 y += _fontHeight + leading;
1860 if (y >= _bounds.height()) {
1861 ++_maxScroll;
1864 // Start a new record on the next line.
1865 rec.clearGlyphs();
1866 rec.setXOffset(x);
1867 rec.setYOffset(y);
1869 // TODO : what if m_text_glyph_records is empty ?
1870 // Is it possible ?
1871 assert(!_textRecords.empty());
1872 SWF::TextRecord& last_line = _textRecords.back();
1874 linestartit = _line_starts.begin();
1875 linestartend = _line_starts.end();
1876 if (last_space_glyph == -1)
1878 // Pull the previous glyph down onto the
1879 // new line.
1880 if (!last_line.glyphs().empty())
1882 rec.addGlyph(last_line.glyphs().back());
1883 x += last_line.glyphs().back().advance;
1884 previous_x -= last_line.glyphs().back().advance;
1885 last_line.clearGlyphs(1);
1886 //record the new line start
1888 const size_t currentPos = _glyphcount;
1889 while (linestartit != linestartend &&
1890 *linestartit + 1 <= currentPos)
1892 linestartit++;
1894 _line_starts.insert(linestartit, currentPos);
1895 _recordStarts.push_back(currentPos);
1897 } else {
1898 // Move the previous word down onto the next line.
1900 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1902 const SWF::TextRecord::Glyphs::size_type lineSize =
1903 last_line.glyphs().size();
1904 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1905 ++i)
1907 rec.addGlyph(last_line.glyphs()[i]);
1908 x += last_line.glyphs()[i].advance;
1909 previous_x -= last_line.glyphs()[i].advance;
1911 last_line.clearGlyphs(lineSize - last_space_glyph);
1913 // record the position at the start of this line as
1914 // a line_start
1915 const size_t linestartpos = _glyphcount -
1916 rec.glyphs().size();
1918 while (linestartit < linestartend &&
1919 *linestartit < linestartpos)
1921 ++linestartit;
1923 _line_starts.insert(linestartit, linestartpos);
1924 _recordStarts.push_back(linestartpos);
1927 align_line(getTextAlignment(), last_line_start_record, previous_x);
1929 last_space_glyph = -1;
1930 last_line_start_record = _textRecords.size();
1933 else
1935 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1936 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1937 #endif
1944 TextField::getDefinitionVersion() const
1946 // TODO: work out if this correct.
1947 return get_root()->getDefinitionVersion();
1951 TextField::VariableRef
1952 TextField::parseTextVariableRef(const std::string& variableName) const
1954 VariableRef ret;
1955 ret.first = 0;
1957 #ifdef DEBUG_DYNTEXT_VARIABLES
1958 log_debug(_("VariableName: %s"), variableName);
1959 #endif
1961 /// Why isn't get_environment const again ?
1962 const as_environment& env = const_cast<TextField*>(this)->get_environment();
1964 as_object* target = getObject(env.target());
1965 if (!target) {
1966 IF_VERBOSE_MALFORMED_SWF(
1967 log_swferror(_("Current environment has no target, "
1968 "can't bind VariableName (%s) associated to "
1969 "text field. Gnash will try to register "
1970 "again on next access."), variableName);
1972 return ret;
1975 // If the variable string contains a path, we extract
1976 // the appropriate target from it and update the variable
1977 // name. We copy the string so we can assign to it if necessary.
1978 std::string parsedName = variableName;
1979 std::string path, var;
1980 if (parsePath(variableName, path, var))
1982 #ifdef DEBUG_DYNTEXT_VARIABLES
1983 log_debug(_("Variable text Path: %s, Var: %s"), path, var);
1984 #endif
1985 // find target for the path component
1986 // we use our parent's environment for this
1987 target = findObject(env, path);
1989 parsedName = var;
1992 if ( ! target )
1994 IF_VERBOSE_MALFORMED_SWF(
1995 log_swferror(_("VariableName associated to text field refers "
1996 "to an unknown target (%s). It is possible that the "
1997 "DisplayObject will be instantiated later in the SWF "
1998 "stream. Gnash will try to register again on next "
1999 "access."), path);
2001 return ret;
2004 ret.first = target;
2005 ret.second = getURI(getVM(*object()), parsedName);
2007 return ret;
2010 void
2011 TextField::registerTextVariable()
2013 //#define DEBUG_DYNTEXT_VARIABLES 1
2015 #ifdef DEBUG_DYNTEXT_VARIABLES
2016 log_debug(_("registerTextVariable() called"));
2017 #endif
2019 if (_text_variable_registered) {
2020 #ifdef DEBUG_DYNTEXT_VARIABLES
2021 log_debug(_("registerTextVariable() no-op call (already registered)"));
2022 #endif
2023 return;
2026 if (_variable_name.empty()) {
2027 #ifdef DEBUG_DYNTEXT_VARIABLES
2028 log_debug(_("string is empty, consider as registered"));
2029 #endif
2030 _text_variable_registered=true;
2031 return;
2034 VariableRef varRef = parseTextVariableRef(_variable_name);
2035 as_object* target = varRef.first;
2036 if (!target) {
2037 log_debug(_("VariableName associated to text field (%s) refer to "
2038 "an unknown target. It is possible that the DisplayObject "
2039 "will be instantiated later in the SWF stream. "
2040 "Gnash will try to register again on next access."),
2041 _variable_name);
2042 return;
2045 const ObjectURI& key = varRef.second;
2046 as_object* obj = getObject(this);
2047 const int version = getSWFVersion(*obj);
2048 string_table& st = getStringTable(*obj);
2050 // check if the VariableName already has a value,
2051 // in that case update text value
2052 as_value val;
2053 if (target->get_member(key, &val) ) {
2054 #ifdef DEBUG_DYNTEXT_VARIABLES
2055 log_debug(_("target object (%s @ %p) does have a member named %s"),
2056 typeName(*target), (void*)target, st.value(key));
2057 #endif
2058 // TODO: pass environment to to_string ?
2059 // as_environment& env = get_environment();
2060 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
2062 else if (_textDefined) {
2063 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
2064 #ifdef DEBUG_DYNTEXT_VARIABLES
2065 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2066 "named %s (no problem, we'll add it with value %s)"),
2067 typeName(*target), (void*)target,
2068 st.value(key), newVal);
2069 #endif
2070 target->set_member(key, newVal);
2072 else {
2073 #ifdef DEBUG_DYNTEXT_VARIABLES
2074 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2075 "named %s, and we don't have text defined"),
2076 typeName(*target), (void*)target, st.value(key));
2077 #endif
2080 MovieClip* sprite = get<MovieClip>(target);
2082 if (sprite) {
2083 // add the textfield variable to the target sprite
2084 // TODO: have set_textfield_variable take a string_table::key instead ?
2085 #ifdef DEBUG_DYNTEXT_VARIABLES
2086 log_debug("Calling set_textfield_variable(%s) against sprite %s",
2087 st.value(key), sprite->getTarget());
2088 #endif
2089 sprite->set_textfield_variable(key.toString(st), this);
2092 _text_variable_registered=true;
2096 /// Parses an HTML tag (between < and >) and puts
2097 /// the contents into tag. Returns false if the
2098 /// tag was incomplete. The iterator is moved to after
2099 /// the closing tag or the end of the string.
2100 bool
2101 TextField::parseHTML(std::wstring& tag,
2102 std::map<std::string, std::string>& attributes,
2103 std::wstring::const_iterator& it,
2104 const std::wstring::const_iterator& e,
2105 bool& selfclosing) const
2107 while (it != e && *it != ' ') {
2108 if (*it == '/') {
2109 ++it;
2110 if (*it == '>') {
2111 ++it;
2112 selfclosing = true;
2113 return true;
2114 } else {
2115 while (it != e) {
2116 ++it;
2118 log_error("invalid html tag");
2119 return false;
2122 if (*it == '>') {
2123 ++it;
2124 return true;
2127 // Check for NULL character
2128 if (*it == 0) {
2129 log_error("found NULL character in htmlText");
2130 return false;
2132 tag.push_back(std::toupper(*it));
2133 ++it;
2135 while (it != e && *it == ' ') {
2136 ++it; //skip over spaces
2138 if (*it == '>') {
2139 ++it;
2140 return true;
2142 if (*it == '/') {
2143 ++it;
2144 if (*it == '>') {
2145 ++it;
2146 selfclosing = true;
2147 return true;
2148 } else {
2149 while (it != e) {
2150 ++it;
2152 log_error("invalid html tag");
2153 return false;
2157 std::string attname;
2158 std::string attvalue;
2160 //attributes
2161 while (it != e && *it != '>') {
2162 while (it != e && *it != '=' && *it != ' ') {
2164 if (*it == 0) {
2165 log_error("found NULL character in htmlText");
2166 return false;
2168 if (*it == '>') {
2169 log_error("malformed HTML tag, invalid attribute name");
2170 while (it != e) {
2171 ++it;
2173 return false;
2176 attname.push_back(std::toupper(*it));
2177 ++it;
2179 while (it != e && (*it == ' ' || *it == '=')) {
2180 ++it; //skip over spaces and '='
2183 if (it == e) return false;
2184 const char q = *it;
2185 if (q != '"' && q != '\'') {
2186 // This is not an attribute.
2187 while (it != e) ++it;
2188 return false;
2191 // Advance past attribute opener
2192 ++it;
2193 while (it != e && *it != q) {
2195 if (*it == 0) {
2196 log_error("found NULL character in htmlText");
2197 return false;
2200 attvalue.push_back(std::toupper(*it));
2201 ++it;
2204 if (it == e) return false;
2206 if (*it != q) {
2207 while (it != e) ++it;
2208 return false;
2211 // Skip attribute closer.
2212 ++it;
2214 attributes.insert(std::make_pair(attname, attvalue));
2215 attname.clear();
2216 attvalue.clear();
2218 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2219 log_error("malformed HTML tag, invalid attribute value");
2220 while (it != e) {
2221 ++it;
2223 return false;
2225 if (*it == ' ') {
2226 while (it != e && *it == ' ') {
2227 ++it; //skip over spaces
2230 if (*it == '>') {
2231 ++it;
2232 return true;
2233 } else if (*it == '/') {
2234 ++it;
2235 if (*it == '>') {
2236 ++it;
2237 selfclosing = true;
2238 return true;
2239 } else {
2240 while (it != e) {
2241 ++it;
2243 log_error("invalid html tag");
2244 return false;
2249 #ifdef GNASH_DEBUG_TEXTFIELDS
2250 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2251 #endif
2252 log_error("I declare this a HTML syntax error");
2253 return false; //since we did not return already, must be malformed...?
2256 void
2257 TextField::set_variable_name(const std::string& newname)
2259 if ( newname != _variable_name )
2261 _variable_name = newname;
2263 // The name was empty or undefined, so there's nothing more to do.
2264 if (_variable_name.empty()) return;
2266 _text_variable_registered = false;
2268 #ifdef DEBUG_DYNTEXT_VARIABLES
2269 log_debug("Calling updateText after change of variable name");
2270 #endif
2272 // Use the original definition text if this isn't dynamically
2273 // created.
2274 if (_tag) updateText(_tag->defaultText());
2276 #ifdef DEBUG_DYNTEXT_VARIABLES
2277 log_debug("Calling registerTextVariable after change of variable "
2278 "name and updateText call");
2279 #endif
2280 registerTextVariable();
2284 bool
2285 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2287 const SWFMatrix wm = getWorldMatrix(*this).invert();
2288 point lp(x, y);
2289 wm.transform(lp);
2290 return _bounds.point_test(lp.x, lp.y);
2293 bool
2294 TextField::getDrawBorder() const
2296 return _drawBorder;
2299 void
2300 TextField::setDrawBorder(bool val)
2302 if ( _drawBorder != val )
2304 set_invalidated();
2305 _drawBorder = val;
2309 rgba
2310 TextField::getBorderColor() const
2312 return _borderColor;
2315 void
2316 TextField::setBorderColor(const rgba& col)
2318 if ( _borderColor != col )
2320 set_invalidated();
2321 _borderColor = col;
2325 bool
2326 TextField::getDrawBackground() const
2328 return _drawBackground;
2331 void
2332 TextField::setDrawBackground(bool val)
2334 if ( _drawBackground != val )
2336 set_invalidated();
2337 _drawBackground = val;
2341 rgba
2342 TextField::getBackgroundColor() const
2344 return _backgroundColor;
2347 void
2348 TextField::setBackgroundColor(const rgba& col)
2350 if ( _backgroundColor != col )
2352 set_invalidated();
2353 _backgroundColor = col;
2357 void
2358 TextField::setTextColor(const rgba& col)
2360 if (_textColor != col) {
2362 set_invalidated();
2363 _textColor = col;
2364 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2365 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2369 void
2370 TextField::setEmbedFonts(bool use)
2372 if ( _embedFonts != use )
2374 set_invalidated();
2375 _embedFonts=use;
2376 format_text();
2380 void
2381 TextField::setWordWrap(bool wrap)
2383 if (_wordWrap != wrap) {
2385 set_invalidated();
2386 _wordWrap = wrap;
2387 format_text();
2391 void
2392 TextField::setLeading(boost::int16_t h)
2394 if ( _leading != h )
2396 set_invalidated();
2397 _leading = h;
2401 void
2402 TextField::setUnderlined(bool v)
2404 if ( _underlined != v )
2406 set_invalidated();
2407 _underlined = v;
2411 void
2412 TextField::setBullet(bool b)
2414 if (_bullet != b) {
2415 _bullet = b;
2419 void
2420 TextField::setTabStops(const std::vector<int>& tabStops)
2422 _tabStops.resize(tabStops.size());
2424 for (size_t i = 0; i < tabStops.size(); i ++) {
2425 _tabStops[i] = pixelsToTwips(tabStops[i]);
2428 set_invalidated();
2431 void
2432 TextField::setURL(std::string url)
2434 if ( _url != url ) {
2435 set_invalidated();
2436 _url = url;
2440 void
2441 TextField::setTarget(std::string target)
2443 if ( _target != target)
2445 set_invalidated();
2446 _target = target;
2450 void
2451 TextField::setDisplay(TextFormatDisplay display)
2453 if ( _display != display )
2455 set_invalidated();
2456 _display = display;
2460 void
2461 TextField::setAlignment(TextAlignment h)
2463 if ( _alignment != h )
2465 set_invalidated();
2466 _alignment = h;
2470 void
2471 TextField::setIndent(boost::uint16_t h)
2473 if ( _indent != h )
2475 set_invalidated();
2476 _indent = h;
2480 void
2481 TextField::setBlockIndent(boost::uint16_t h)
2483 if ( _blockIndent != h )
2485 set_invalidated();
2486 _blockIndent = h;
2490 void
2491 TextField::setRightMargin(boost::uint16_t h)
2493 if ( _rightMargin != h )
2495 set_invalidated();
2496 _rightMargin = h;
2500 void
2501 TextField::setLeftMargin(boost::uint16_t h)
2503 if (_leftMargin != h)
2505 set_invalidated();
2506 _leftMargin = h;
2510 void
2511 TextField::setFontHeight(boost::uint16_t h)
2513 if ( _fontHeight != h )
2515 set_invalidated();
2516 _fontHeight = h;
2521 TextField::TypeValue
2522 TextField::parseTypeValue(const std::string& val)
2524 StringNoCaseEqual cmp;
2526 if (cmp(val, "input")) return typeInput;
2527 if (cmp(val, "dynamic")) return typeDynamic;
2528 return typeInvalid;
2533 const char*
2534 TextField::typeValueName(TypeValue val)
2536 switch (val)
2538 case typeInput:
2539 //log_debug("typeInput returned as 'input'");
2540 return "input";
2541 case typeDynamic:
2542 //log_debug("typeDynamic returned as 'dynamic'");
2543 return "dynamic";
2544 default:
2545 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2546 return "invalid";
2551 void
2552 TextField::setAutoSize(AutoSize val)
2554 if ( val == _autoSize ) return;
2556 set_invalidated();
2558 _autoSize = val;
2559 format_text();
2562 TextField::TextAlignment
2563 TextField::getTextAlignment()
2565 TextAlignment textAlignment = getAlignment();
2567 switch (_autoSize) {
2568 case AUTOSIZE_CENTER:
2569 textAlignment = ALIGN_CENTER;
2570 break;
2571 case AUTOSIZE_LEFT:
2572 textAlignment = ALIGN_LEFT;
2573 break;
2574 case AUTOSIZE_RIGHT:
2575 textAlignment = ALIGN_RIGHT;
2576 break;
2577 default:
2578 // Leave it as it was.
2579 break;
2582 return textAlignment;
2585 void
2586 TextField::onChanged()
2588 as_object* obj = getObject(this);
2589 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2592 /// This is called by movie_root when focus is applied to this TextField.
2594 /// The return value is true if the TextField can receive focus.
2595 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2596 /// handle focus.
2597 bool
2598 TextField::handleFocus()
2601 set_invalidated();
2603 /// Select the entire text on focus.
2604 setSelection(0, _text.length());
2606 m_has_focus = true;
2608 m_cursor = _text.size();
2609 format_text();
2610 return true;
2613 /// This is called by movie_root when focus is removed from the
2614 /// current TextField.
2615 void
2616 TextField::killFocus()
2618 if ( ! m_has_focus ) return; // nothing to do
2620 set_invalidated();
2621 m_has_focus = false;
2623 format_text(); // is this needed ?
2627 void
2628 TextField::setWidth(double newwidth)
2630 const SWFRect& bounds = getBounds();
2631 _bounds.set_to_rect(bounds.get_x_min(),
2632 bounds.get_y_min(),
2633 bounds.get_x_min() + newwidth,
2634 bounds.get_y_max());
2637 void
2638 TextField::setHeight(double newheight)
2640 const SWFRect& bounds = getBounds();
2641 _bounds.set_to_rect(bounds.get_x_min(),
2642 bounds.get_y_min(),
2643 bounds.get_x_max(),
2644 bounds.get_y_min() + newheight);
2647 /// TextField interface functions
2650 } // namespace gnash
2653 // Local Variables:
2654 // mode: C++
2655 // indent-tabs-mode: t
2656 // End: