diff options
Diffstat (limited to 'indra/llui/lltexteditor.cpp')
-rw-r--r-- | indra/llui/lltexteditor.cpp | 3022 |
1 files changed, 589 insertions, 2433 deletions
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 34bced064e..94bf716e7d 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1,32 +1,25 @@ /** * @file lltexteditor.cpp - * @brief LLTextEditor base class * - * $LicenseInfo:firstyear=2001&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -34,9 +27,13 @@ #include "linden_common.h" +#define LLTEXTEDITOR_CPP #include "lltexteditor.h" +#include "llfontfreetype.h" // for LLFontFreetype::FIRST_CHAR #include "llfontgl.h" +#include "llgl.h" // LLGLSUIDefault() +#include "lllocalcliprect.h" #include "llrender.h" #include "llui.h" #include "lluictrlfactory.h" @@ -45,7 +42,6 @@ #include "lltimer.h" #include "llmath.h" -#include "audioengine.h" #include "llclipboard.h" #include "llscrollbar.h" #include "llstl.h" @@ -55,55 +51,56 @@ #include "llundo.h" #include "llviewborder.h" #include "llcontrol.h" -#include "llimagegl.h" #include "llwindow.h" #include "lltextparser.h" +#include "llscrollcontainer.h" +#include "llpanel.h" +#include "llurlregistry.h" +#include "lltooltip.h" +#include "llmenugl.h" + #include <queue> +#include "llcombobox.h" // // Globals // -static LLDefaultWidgetRegistry::Register<LLTextEditor> r("simple_text_editor"); +static LLDefaultChildRegistry::Register<LLTextEditor> r("simple_text_editor"); + +// Compiler optimization, generate extern template +template class LLTextEditor* LLView::getChild<class LLTextEditor>( + const std::string& name, BOOL recurse) const; // // Constants // const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32; const S32 UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4; -const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds -const S32 CURSOR_THICKNESS = 2; const S32 SPACES_PER_TAB = 4; - -LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; -void (* LLTextEditor::mURLcallback)(const std::string&) = NULL; -bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; -bool (* LLTextEditor::mSecondlifeURLcallbackRightClick)(const std::string&) = NULL; - - /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdInsert : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdInsert : public LLTextBase::TextCmd { public: - LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws) - : LLTextCmd(pos, group_with_next), mWString(ws) + TextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws, LLTextSegmentPtr segment) + : TextCmd(pos, group_with_next, segment), mWString(ws) { } - virtual ~LLTextCmdInsert() {} - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + virtual ~TextCmdInsert() {} + virtual BOOL execute( LLTextBase* editor, S32* delta ) { *delta = insert(editor, getPosition(), mWString ); LLWStringUtil::truncate(mWString, *delta); //mWString = wstring_truncate(mWString, *delta); return (*delta != 0); } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { remove(editor, getPosition(), mWString.length() ); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { insert(editor, getPosition(), mWString ); return getPosition() + mWString.length(); @@ -114,11 +111,11 @@ private: }; /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdAddChar : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdAddChar : public LLTextBase::TextCmd { public: - LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc) - : LLTextCmd(pos, group_with_next), mWString(1, wc), mBlockExtensions(FALSE) + TextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc, LLTextSegmentPtr segment) + : TextCmd(pos, group_with_next, segment), mWString(1, wc), mBlockExtensions(FALSE) { } virtual void blockExtensions() @@ -127,16 +124,19 @@ public: } virtual BOOL canExtend(S32 pos) const { + // cannot extend text with custom segments + if (!mSegments.empty()) return FALSE; + return !mBlockExtensions && (pos == getPosition() + (S32)mWString.length()); } - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + virtual BOOL execute( LLTextBase* editor, S32* delta ) { *delta = insert(editor, getPosition(), mWString); LLWStringUtil::truncate(mWString, *delta); //mWString = wstring_truncate(mWString, *delta); return (*delta != 0); } - virtual BOOL extendAndExecute( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta ) + virtual BOOL extendAndExecute( LLTextBase* editor, S32 pos, llwchar wc, S32* delta ) { LLWString ws; ws += wc; @@ -148,12 +148,12 @@ public: } return (*delta != 0); } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { remove(editor, getPosition(), mWString.length() ); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { insert(editor, getPosition(), mWString ); return getPosition() + mWString.length(); @@ -167,25 +167,25 @@ private: /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdOverwriteChar : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdOverwriteChar : public LLTextBase::TextCmd { public: - LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) - : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} + TextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) + : TextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + virtual BOOL execute( LLTextBase* editor, S32* delta ) { - mOldChar = editor->getWChar(getPosition()); + mOldChar = editor->getWText()[getPosition()]; overwrite(editor, getPosition(), mChar); *delta = 0; return TRUE; } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { overwrite(editor, getPosition(), mOldChar); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { overwrite(editor, getPosition(), mChar); return getPosition()+1; @@ -198,25 +198,26 @@ private: /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdRemove : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdRemove : public LLTextBase::TextCmd { public: - LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) : - LLTextCmd(pos, group_with_next), mLen(len) + TextCmdRemove( S32 pos, BOOL group_with_next, S32 len, segment_vec_t& segments ) : + TextCmd(pos, group_with_next), mLen(len) { + std::swap(mSegments, segments); } - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + virtual BOOL execute( LLTextBase* editor, S32* delta ) { - mWString = editor->getWSubString(getPosition(), mLen); + mWString = editor->getWText().substr(getPosition(), mLen); *delta = remove(editor, getPosition(), mLen ); return (*delta != 0); } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { - insert(editor, getPosition(), mWString ); + insert(editor, getPosition(), mWString); return getPosition() + mWString.length(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { remove(editor, getPosition(), mLen ); return getPosition(); @@ -228,352 +229,107 @@ private: /////////////////////////////////////////////////////////////////// -LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) - : LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), - mMaxTextByteLength( p.max_text_length ), +LLTextEditor::Params::Params() +: default_text("default_text"), + prevalidate_callback("prevalidate_callback"), + embedded_items("embedded_items", false), + ignore_tab("ignore_tab", true), + show_line_numbers("show_line_numbers", false), + default_color("default_color"), + commit_on_focus_lost("commit_on_focus_lost", false), + show_context_menu("show_context_menu") +{ + addSynonym(prevalidate_callback, "text_type"); +} + +LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : + LLTextBase(p), mBaseDocIsPristine(TRUE), mPristineCmd( NULL ), mLastCmd( NULL ), - mCursorPos( 0 ), - mIsSelecting( FALSE ), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mScrolledToBottom( TRUE ), - mOnScrollEndCallback( NULL ), - mOnScrollEndData( NULL ), - mCursorColor( p.cursor_color() ), - mFgColor( p.text_color() ), mDefaultColor( p.default_color() ), - mReadOnlyFgColor( p.text_readonly_color() ), - mWriteableBgColor( p.bg_writeable_color() ), - mReadOnlyBgColor( p.bg_readonly_color() ), - mFocusBgColor( p.bg_focus_color() ), - mReadOnly(p.read_only), - mWordWrap( p.word_wrap ), - mShowLineNumbers ( FALSE ), - mCommitOnFocusLost( FALSE ), - mHideScrollbarForShortDocs( FALSE ), - mTakesNonScrollClicks( p.takes_non_scroll_clicks ), - mTrackBottom( p.track_bottom ), - mAllowEmbeddedItems( p.allow_embedded_items ), - mAcceptCallingCardNames(FALSE), - mHandleEditKeysDirectly( FALSE ), + mShowLineNumbers ( p.show_line_numbers ), + mCommitOnFocusLost( p.commit_on_focus_lost), + mAllowEmbeddedItems( p.embedded_items ), mMouseDownX(0), mMouseDownY(0), - mLastSelectionX(-1), - mReflowNeeded(FALSE), - mScrollNeeded(FALSE), - mLastSelectionY(-1), mTabsToNextField(p.ignore_tab), - mGLFont(p.font), - mGLFontStyle(LLFontGL::getStyleFromString(p.font.style)) + mPrevalidateFunc(p.prevalidate_callback()), + mContextMenu(NULL), + mShowContextMenu(p.show_context_menu) { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); - mSourceID.generate(); - // reset desired x cursor position - mDesiredXPixel = -1; - - updateTextRect(); - - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 page_size = mTextRect.getHeight() / line_height; - - // Init the scrollbar - LLRect scroll_rect; - scroll_rect.setOriginAndSize( - getRect().getWidth() - scrollbar_size, - 1, - scrollbar_size, - getRect().getHeight() - 1); - S32 lines_in_doc = getLineCount(); - LLScrollbar::Params sbparams; - sbparams.name("Scrollbar"); - sbparams.rect(scroll_rect); - sbparams.orientation(LLScrollbar::VERTICAL); - sbparams.doc_size(lines_in_doc); - sbparams.doc_pos(0); - sbparams.page_size(page_size); - sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); - mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams); - mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData); - addChild(mScrollbar); - - static LLUICachedControl<S32> text_editor_border ("UITextEditorBorder", 0); + //FIXME: use image? LLViewBorder::Params params; - params.name("text ed border"); - params.rect(getLocalRect()); - params.bevel_type(LLViewBorder::BEVEL_IN); - params.border_thickness(text_editor_border); + params.name = "text ed border"; + params.rect = getLocalRect(); + params.bevel_style = LLViewBorder::BEVEL_IN; + params.border_thickness = 1; + params.visible = p.border_visible; mBorder = LLUICtrlFactory::create<LLViewBorder> (params); addChild( mBorder ); - mBorder->setVisible(!p.hide_border); - appendText(p.default_text, FALSE, FALSE); + setText(p.default_text()); - setHideScrollbarForShortDocs(p.hide_scrollbar); - - mParseHTML=FALSE; - mHTML.clear(); + if (mShowLineNumbers) + { + mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; + updateRects(); + } } void LLTextEditor::initFromParams( const LLTextEditor::Params& p) { - resetDirty(); // Update saved text state - LLUICtrl::initFromParams(p); - // HACK: work around enabled == readonly design bug -- RN - // setEnabled will modify our read only status, so do this after - // LLUICtrl::initFromParams - if (p.read_only.isProvided()) - { - mReadOnly = p.read_only; - updateSegments(); - updateAllowingLanguageInput(); - } + LLTextBase::initFromParams(p); + // HACK: text editors always need to be enabled so that we can scroll LLView::setEnabled(true); -} -LLTextEditor::~LLTextEditor() -{ - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() - - // Route menu back to the default - if( gEditMenuHandler == this ) + if (p.commit_on_focus_lost.isProvided()) { - gEditMenuHandler = NULL; + mCommitOnFocusLost = p.commit_on_focus_lost; } - - // Scrollbar is deleted by LLView - mHoverSegment = NULL; - std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); - - std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); -} - -LLTextViewModel* LLTextEditor::getViewModel() const -{ - return (LLTextViewModel*)mViewModel.get(); -} - -void LLTextEditor::setTrackColor( const LLColor4& color ) -{ - mScrollbar->setTrackColor(color); -} - -void LLTextEditor::setThumbColor( const LLColor4& color ) -{ - mScrollbar->setThumbColor(color); + + updateAllowingLanguageInput(); } -void LLTextEditor::updateLineStartList(S32 startpos) +LLTextEditor::~LLTextEditor() { - updateSegments(); - - bindEmbeddedChars(mGLFont); - - S32 seg_num = mSegments.size(); - S32 seg_idx = 0; - S32 seg_offset = 0; - - if (!mLineStartList.empty()) - { - getSegmentAndOffset(startpos, &seg_idx, &seg_offset); - line_info t(seg_idx, seg_offset); - line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare()); - if (iter != mLineStartList.begin()) --iter; - seg_idx = iter->mSegment; - seg_offset = iter->mOffset; - mLineStartList.erase(iter, mLineStartList.end()); - } - - LLWString text(getWText()); - while( seg_idx < seg_num ) - { - mLineStartList.push_back(line_info(seg_idx,seg_offset)); - BOOL line_ended = FALSE; - S32 start_x = mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0; - S32 line_width = start_x; - while(!line_ended && seg_idx < seg_num) - { - LLTextSegment* segment = mSegments[seg_idx]; - S32 start_idx = segment->getStart() + seg_offset; - S32 end_idx = start_idx; - while (end_idx < segment->getEnd() && text[end_idx] != '\n') - { - end_idx++; - } - if (start_idx == end_idx) - { - if (end_idx >= segment->getEnd()) - { - // empty segment - seg_idx++; - seg_offset = 0; - } - else - { - // empty line - line_ended = TRUE; - seg_offset++; - } - } - else - { - const llwchar* str = text.c_str() + start_idx; - S32 drawn = mGLFont->maxDrawableChars(str, (F32)abs(mTextRect.getWidth()) - line_width, - end_idx - start_idx, mWordWrap, mAllowEmbeddedItems ); - if( 0 == drawn && line_width == start_x) - { - // If at the beginning of a line, draw at least one character, even if it doesn't all fit. - drawn = 1; - } - seg_offset += drawn; - line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems); - end_idx = segment->getStart() + seg_offset; - if (end_idx < segment->getEnd()) - { - line_ended = TRUE; - if (text[end_idx] == '\n') - { - seg_offset++; // skip newline - } - } - else - { - // finished with segment - seg_idx++; - seg_offset = 0; - } - } - } - } - - unbindEmbeddedChars(mGLFont); + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid - mScrollbar->setDocSize( getLineCount() ); - - if (mHideScrollbarForShortDocs) - { - BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); - mScrollbar->setVisible(!short_doc); - } + // Scrollbar is deleted by LLView + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - // if scrolled to bottom, stay at bottom - // unless user is selecting text - // do this after updating page size - if (mScrolledToBottom && mTrackBottom && !hasMouseCapture()) - { - endOfDoc(); - } + // context menu is owned by menu holder, not us + //delete mContextMenu; } //////////////////////////////////////////////////////////// // LLTextEditor // Public methods -BOOL LLTextEditor::truncate() +void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { - BOOL did_truncate = FALSE; - - // First rough check - if we're less than 1/4th the size, we're OK - if (getLength() >= S32(mMaxTextByteLength / 4)) - { - // Have to check actual byte size - LLWString text(getWText()); - S32 utf8_byte_size = wstring_utf8_length(text); - if ( utf8_byte_size > mMaxTextByteLength ) + // validate incoming text if necessary + if (mPrevalidateFunc) + { + LLWString test_text = utf8str_to_wstring(utf8str); + if (!mPrevalidateFunc(test_text)) { - // Truncate safely in UTF-8 - std::string temp_utf8_text = wstring_to_utf8str(text); - temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); - getViewModel()->setDisplay(utf8str_to_wstring( temp_utf8_text )); - did_truncate = TRUE; + // not valid text, nothing to do + return; } } - return did_truncate; -} - -void LLTextEditor::setText(const LLStringExplicit &utf8str) -{ - // LLStringUtil::removeCRLF(utf8str); - mViewModel->setValue(utf8str_removeCRLF(utf8str)); - - truncate(); blockUndo(); - - setCursorPos(0); - deselect(); - - needsReflow(); - - resetDirty(); -} - -void LLTextEditor::setWText(const LLWString &wtext) -{ - getViewModel()->setDisplay(wtext); - - truncate(); - blockUndo(); - - setCursorPos(0); deselect(); - needsReflow(); + LLTextBase::setText(utf8str, input_params); resetDirty(); } -// virtual -void LLTextEditor::setValue(const LLSD& value) -{ - setText(value.asString()); -} - -std::string LLTextEditor::getText() const -{ - if (mAllowEmbeddedItems) - { - llwarns << "getText() called on text with embedded items (not supported)" << llendl; - } - return mViewModel->getValue().asString(); -} - -void LLTextEditor::setWordWrap(BOOL b) -{ - mWordWrap = b; - - setCursorPos(0); - deselect(); - - needsReflow(); -} - - -void LLTextEditor::setBorderVisible(BOOL b) -{ - mBorder->setVisible(b); -} - -BOOL LLTextEditor::isBorderVisible() const -{ - return mBorder->getVisible(); -} - -void LLTextEditor::setHideScrollbarForShortDocs(BOOL b) -{ - mHideScrollbarForShortDocs = b; - - if (mHideScrollbarForShortDocs) - { - BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); - mScrollbar->setVisible(!short_doc); - } -} - void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap) { if (search_text_in.empty()) @@ -596,7 +352,7 @@ void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insen if (selected_text == search_text) { // We already have this word selected, we are searching for the next. - mCursorPos += search_text.size(); + setCursorPos(mCursorPos + search_text.size()); } } @@ -659,9 +415,7 @@ BOOL LLTextEditor::replaceText(const std::string& search_text_in, const std::str void LLTextEditor::replaceTextAll(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive) { - S32 cur_pos = mScrollbar->getDocPos(); - - setCursorPos(0); + startOfDoc(); selectNext(search_text, case_insensitive, FALSE); BOOL replaced = TRUE; @@ -669,14 +423,6 @@ void LLTextEditor::replaceTextAll(const std::string& search_text, const std::str { replaced = replaceText(search_text,replace_text, case_insensitive, FALSE); } - - mScrollbar->setDocPos(cur_pos); -} - -// Picks a new cursor position based on the screen size of text being drawn. -void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round ) -{ - setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round)); } S32 LLTextEditor::prevWordPos(S32 cursorPos) const @@ -686,7 +432,7 @@ S32 LLTextEditor::prevWordPos(S32 cursorPos) const { cursorPos--; } - while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) { cursorPos--; } @@ -696,7 +442,7 @@ S32 LLTextEditor::prevWordPos(S32 cursorPos) const S32 LLTextEditor::nextWordPos(S32 cursorPos) const { LLWString wtext(getWText()); - while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos] ) ) + while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) { cursorPos++; } @@ -707,198 +453,49 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const return cursorPos; } -S32 LLTextEditor::getLineStart( S32 line ) const +const LLTextSegmentPtr LLTextEditor::getPreviousSegment() const { - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - S32 segidx = mLineStartList[line].mSegment; - S32 segoffset = mLineStartList[line].mOffset; - LLTextSegment* seg = mSegments[segidx]; - S32 res = seg->getStart() + segoffset; - if (res > seg->getEnd()) llerrs << "wtf" << llendl; - return res; -} + static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment; -// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. -void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp ) const -{ - if (mLineStartList.empty()) + index_segment->setStart(mCursorPos); + index_segment->setEnd(mCursorPos); + + // find segment index at character to left of cursor (or rightmost edge of selection) + segment_set_t::const_iterator it = mSegments.lower_bound(index_segment); + + if (it != mSegments.end()) { - *linep = 0; - *offsetp = startpos; + return *it; } else { - S32 seg_idx, seg_offset; - getSegmentAndOffset( startpos, &seg_idx, &seg_offset ); - - line_info tline(seg_idx, seg_offset); - line_list_t::const_iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare()); - if (iter != mLineStartList.begin()) --iter; - *linep = iter - mLineStartList.begin(); - S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset; - *offsetp = startpos - line_start; + return LLTextSegmentPtr(); } } -void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) const -{ - if (mSegments.empty()) - { - *segidxp = -1; - *offsetp = startpos; - } - - LLTextSegment tseg(startpos); - segment_list_t::const_iterator seg_iter; - seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare()); - if (seg_iter != mSegments.begin()) --seg_iter; - *segidxp = seg_iter - mSegments.begin(); - *offsetp = startpos - (*seg_iter)->getStart(); -} - -const LLTextSegment* LLTextEditor::getPreviousSegment() const -{ - // find segment index at character to left of cursor (or rightmost edge of selection) - S32 idx = llmax(0, getSegmentIdxAtOffset(mCursorPos) - 1); - return idx >= 0 ? mSegments[idx] : NULL; -} - -void LLTextEditor::getSelectedSegments(std::vector<const LLTextSegment*>& segments) const +void LLTextEditor::getSelectedSegments(LLTextEditor::segment_vec_t& segments) const { S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos; S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos; - S32 first_idx = llmax(0, getSegmentIdxAtOffset(left)); - S32 last_idx = llmax(0, first_idx, getSegmentIdxAtOffset(right)); - for (S32 idx = first_idx; idx <= last_idx; ++idx) - { - segments.push_back(mSegments[idx]); - } + return getSegmentsInRange(segments, left, right, true); } -S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const +void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, S32 start, S32 end, bool include_partial) const { - if(mShowLineNumbers) - { - local_x -= UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - } - - // If round is true, if the position is on the right half of a character, the cursor - // will be put to its right. If round is false, the cursor will always be put to the - // character's left. - - // Figure out which line we're nearest to. - S32 total_lines = getLineCount(); - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 max_visible_lines = mTextRect.getHeight() / line_height; - S32 scroll_lines = mScrollbar->getDocPos(); - S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines ); // Lines currently visible - - //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) ); - S32 line = (mTextRect.mTop - 1 - local_y) / line_height; - if (line >= total_lines) - { - return getLength(); // past the end - } - - line = llclamp( line, 0, visible_lines ) + scroll_lines; - - S32 line_start = getLineStart(line); - S32 next_start = getLineStart(line+1); - S32 line_end = (next_start != line_start) ? next_start - 1 : getLength(); + segment_set_t::const_iterator first_it = getSegIterContaining(start); + segment_set_t::const_iterator end_it = getSegIterContaining(end - 1); + if (end_it != mSegments.end()) ++end_it; - if(line_start == -1) + for (segment_set_t::const_iterator it = first_it; it != end_it; ++it) { - return 0; - } - else - { - S32 line_len = line_end - line_start; - S32 pos; - LLWString text(getWText()); - - if (mAllowEmbeddedItems) - { - // Figure out which character we're nearest to. - bindEmbeddedChars(mGLFont); - pos = mGLFont->charFromPixelOffset(text.c_str(), line_start, - (F32)(local_x - mTextRect.mLeft), - (F32)(mTextRect.getWidth()), - line_len, - round, TRUE); - unbindEmbeddedChars(mGLFont); - } - else + LLTextSegmentPtr segment = *it; + if (include_partial + || (segment->getStart() >= start + && segment->getEnd() <= end)) { - pos = mGLFont->charFromPixelOffset(text.c_str(), line_start, - (F32)(local_x - mTextRect.mLeft), - (F32)mTextRect.getWidth(), - line_len, - round); + segments_out.push_back(segment); } - - return line_start + pos; - } -} - -void LLTextEditor::setCursor(S32 row, S32 column) -{ - LLWString text(getWText()); - const llwchar* doc = text.c_str(); - const char CR = 10; - while(row--) - { - while (CR != *doc++); - } - doc += column; - setCursorPos(doc - text.c_str()); -} - -void LLTextEditor::setCursorPos(S32 offset) -{ - mCursorPos = llclamp(offset, 0, (S32)getLength()); - needsScroll(); - // reset desired x cursor position - mDesiredXPixel = -1; -} - -// virtual -BOOL LLTextEditor::canDeselect() const -{ - return hasSelection(); -} - - -void LLTextEditor::deselect() -{ - mSelectionStart = 0; - mSelectionEnd = 0; - mIsSelecting = FALSE; -} - - -void LLTextEditor::startSelection() -{ - if( !mIsSelecting ) - { - mIsSelecting = TRUE; - mSelectionStart = mCursorPos; - mSelectionEnd = mCursorPos; - } -} - -void LLTextEditor::endSelection() -{ - if( mIsSelecting ) - { - mIsSelecting = FALSE; - mSelectionEnd = mCursorPos; } } @@ -1006,7 +603,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) } right += delta_spaces; - //text = mWText; + text = getWText(); // Find the next new line while( (cur < right) && (text[cur] != '\n') ) @@ -1032,7 +629,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) mSelectionStart = right; mSelectionEnd = left; } - mCursorPos = mSelectionEnd; + setCursorPos(mSelectionEnd); } } @@ -1047,64 +644,25 @@ void LLTextEditor::selectAll() { mSelectionStart = getLength(); mSelectionEnd = 0; - mCursorPos = mSelectionEnd; + setCursorPos(mSelectionEnd); + updatePrimary(); } - -BOOL LLTextEditor::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) +BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { - for ( child_list_const_iter_t child_it = getChildList()->begin(); - child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) - { - return TRUE; - } - } - - if( mSegments.empty() ) - { - return TRUE; - } + BOOL handled = FALSE; - const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); - if( cur_segment ) + // set focus first, in case click callbacks want to change it + // RN: do we really need to have a tab stop? + if (hasTabStop()) { - BOOL has_tool_tip = FALSE; - has_tool_tip = cur_segment->getToolTip( msg ); - - if( has_tool_tip ) - { - // Just use a slop area around the cursor - // Convert rect local to screen coordinates - S32 SLOP = 8; - localPointToScreen( - x - SLOP, y - SLOP, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; - sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; - } + setFocus( TRUE ); } - return TRUE; -} - -BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - // Pretend the mouse is over the scrollbar - return mScrollbar->handleScrollWheel( 0, 0, clicks ); -} - -BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) -{ - BOOL handled = FALSE; // Let scrollbar have first dibs - handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + handled = LLTextBase::handleMouseDown(x, y, mask); - if( !handled && mTakesNonScrollClicks) + if( !handled ) { if (!(mask & MASK_SHIFT)) { @@ -1118,31 +676,10 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) if (mask & MASK_SHIFT) { S32 old_cursor_pos = mCursorPos; - setCursorAtLocalPos( x, y, TRUE ); + setCursorAtLocalPos( x, y, true ); if (hasSelection()) { - /* Mac-like behavior - extend selection towards the cursor - if (mCursorPos < mSelectionStart - && mCursorPos < mSelectionEnd) - { - // ...left of selection - mSelectionStart = llmax(mSelectionStart, mSelectionEnd); - mSelectionEnd = mCursorPos; - } - else if (mCursorPos > mSelectionStart - && mCursorPos > mSelectionEnd) - { - // ...right of selection - mSelectionStart = llmin(mSelectionStart, mSelectionEnd); - mSelectionEnd = mCursorPos; - } - else - { - mSelectionEnd = mCursorPos; - } - */ - // Windows behavior mSelectionEnd = mCursorPos; } else @@ -1155,7 +692,7 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) } else { - setCursorAtLocalPos( x, y, TRUE ); + setCursorAtLocalPos( x, y, true ); startSelection(); } gFocusMgr.setMouseCapture( this ); @@ -1164,26 +701,46 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) handled = TRUE; } - if (hasTabStop()) - { - setFocus( TRUE ); - handled = TRUE; - } - // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); return handled; } +BOOL LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (hasTabStop()) + { + setFocus(TRUE); + } + // Prefer editor menu if it has selection. See EXT-6806. + if (hasSelection() || !LLTextBase::handleRightMouseDown(x, y, mask)) + { + if(getShowContextMenu()) + { + showContextMenu(x, y); + } + } + return TRUE; +} + + BOOL LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { - setFocus( TRUE ); - if( canPastePrimary() ) + if (hasTabStop()) { - setCursorAtLocalPos( x, y, TRUE ); - pastePrimary(); + setFocus(TRUE); + } + + if (!LLTextBase::handleMouseDown(x, y, mask)) + { + if( canPastePrimary() ) + { + setCursorAtLocalPos( x, y, true ); + // does not rely on focus being set + pastePrimary(); + } } return TRUE; } @@ -1191,34 +748,21 @@ BOOL LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); BOOL handled = FALSE; - mHoverSegment = NULL; if(hasMouseCapture() ) { if( mIsSelecting ) { - if (x != mLastSelectionX || y != mLastSelectionY) - { - mLastSelectionX = x; - mLastSelectionY = y; - } - - if( y > mTextRect.mTop ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + if(mScroller) + { + mScroller->autoScroll(x, y); } - else - if( y < mTextRect.mBottom ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); - } - - setCursorAtLocalPos( x, y, TRUE ); + S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); + S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); + setCursorAtLocalPos( clamped_x, clamped_y, true ); mSelectionEnd = mCursorPos; } - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; getWindow()->setCursor(UI_CURSOR_IBEAM); handled = TRUE; @@ -1227,61 +771,21 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) if( !handled ) { // Pass to children - handled = LLView::childrenHandleHover(x, y, mask) != NULL; + handled = LLTextBase::handleHover(x, y, mask); } if( handled ) { // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); } - // Opaque - if( !handled && mTakesNonScrollClicks) + if( !handled ) { - // Check to see if we're over an HTML-style link - if( !mSegments.empty() ) - { - const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); - if( cur_segment ) - { - if(cur_segment->getStyle()->isLink()) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl; - getWindow()->setCursor(UI_CURSOR_HAND); - handled = TRUE; - } - else - if(cur_segment->getStyle()->getIsEmbeddedItem()) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl; - getWindow()->setCursor(UI_CURSOR_HAND); - //getWindow()->setCursor(UI_CURSOR_ARROW); - handled = TRUE; - } - mHoverSegment = cur_segment; - } - } - - if( !handled ) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; - if (!mScrollbar->getVisible() || x < getRect().getWidth() - scrollbar_size) - { - getWindow()->setCursor(UI_CURSOR_IBEAM); - } - else - { - getWindow()->setCursor(UI_CURSOR_ARROW); - } - handled = TRUE; - } + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = TRUE; } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } return handled; } @@ -1290,33 +794,27 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - // let scrollbar have first dibs - handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL; + // if I'm not currently selecting text + if (!(hasSelection() && hasMouseCapture())) + { + // let text segments handle mouse event + handled = LLTextBase::handleMouseUp(x, y, mask); + } - if( !handled && mTakesNonScrollClicks) + if( !handled ) { if( mIsSelecting ) { - // Finish selection - if( y > mTextRect.mTop ) + if(mScroller) { - mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + mScroller->autoScroll(x, y); } - else - if( y < mTextRect.mBottom ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); - } - - setCursorAtLocalPos( x, y, TRUE ); + S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); + S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); + setCursorAtLocalPos( clamped_x, clamped_y, true ); endSelection(); } - if( !hasSelection() ) - { - handleMouseUpOverSegment( x, y, mask ); - } - // take selection to 'primary' clipboard updatePrimary(); @@ -1324,7 +822,7 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) } // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); if( hasMouseCapture() ) { @@ -1341,28 +839,28 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - // let scrollbar have first dibs - handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; + // let scrollbar and text segments have first dibs + handled = LLTextBase::handleDoubleClick(x, y, mask); - if( !handled && mTakesNonScrollClicks) + if( !handled ) { - setCursorAtLocalPos( x, y, FALSE ); + setCursorAtLocalPos( x, y, false ); deselect(); LLWString text = getWText(); - if( isPartOfWord( text[mCursorPos] ) ) + if( LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) { // Select word the cursor is over - while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1])) + while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord(text[mCursorPos-1])) { - mCursorPos--; + if (!setCursorPos(mCursorPos - 1)) break; } startSelection(); - while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) ) + while ((mCursorPos < (S32)text.length()) && LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) { - mCursorPos++; + if (!setCursorPos(mCursorPos + 1)) break; } mSelectionEnd = mCursorPos; @@ -1371,7 +869,7 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { // Select the character the cursor is over startSelection(); - mCursorPos++; + setCursorPos(mCursorPos + 1); mSelectionEnd = mCursorPos; } @@ -1380,7 +878,7 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) mIsSelecting = FALSE; // delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); // take selection to 'primary' clipboard updatePrimary(); @@ -1392,38 +890,36 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) } -// Allow calling cards to be dropped onto text fields. Append the name and -// a carriage return. -// virtual -BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, - BOOL drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - *accept = ACCEPT_NO; - - return TRUE; -} - //---------------------------------------------------------------------------- // Returns change in number of characters in mText -S32 LLTextEditor::execute( LLTextCmd* cmd ) +S32 LLTextEditor::execute( TextCmd* cmd ) { S32 delta = 0; if( cmd->execute(this, &delta) ) { // Delete top of undo stack undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); - if (enditer != mUndoStack.begin()) - { - --enditer; - std::for_each(mUndoStack.begin(), enditer, DeletePointer()); - mUndoStack.erase(mUndoStack.begin(), enditer); - } + std::for_each(mUndoStack.begin(), enditer, DeletePointer()); + mUndoStack.erase(mUndoStack.begin(), enditer); // Push the new command is now on the top (front) of the undo stack. mUndoStack.push_front(cmd); mLastCmd = cmd; + + bool need_to_rollback = mPrevalidateFunc + && !mPrevalidateFunc(getViewModel()->getDisplay()); + if (need_to_rollback) + { + // get rid of this last command and clean up undo stack + undo(); + + // remove any evidence of this command from redo history + mUndoStack.pop_front(); + delete cmd; + + // failure, nothing changed + delta = 0; + } } else { @@ -1434,19 +930,20 @@ S32 LLTextEditor::execute( LLTextCmd* cmd ) return delta; } -S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op) +S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment) { - return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) ); + return execute( new TextCmdInsert( pos, group_with_next_op, wstr, segment ) ); } -S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op) +S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) { - return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) ); -} + S32 end_pos = getEditableIndex(pos + length, true); -S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op) -{ - return insert(getLength(), wstr, group_with_next_op); + segment_vec_t segments_to_remove; + // store text segments + getSegmentsInRange(segments_to_remove, pos, pos + length, false); + + return execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); } S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) @@ -1457,7 +954,7 @@ S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) } else { - return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc)); + return execute(new TextCmdOverwriteChar(pos, FALSE, wc)); } } @@ -1477,8 +974,7 @@ void LLTextEditor::removeCharOrTab() if (text[mCursorPos - 1] == ' ') { // Try to remove a "tab" - S32 line, offset; - getLineAndOffset(mCursorPos, &line, &offset); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); if (offset > 0) { chars_to_remove = offset % SPACES_PER_TAB; @@ -1508,7 +1004,7 @@ void LLTextEditor::removeCharOrTab() } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } @@ -1531,14 +1027,14 @@ void LLTextEditor::removeChar() } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } // Add a single character to the text S32 LLTextEditor::addChar(S32 pos, llwchar wc) { - if ( (wstring_utf8_length( getWText() ) + wchar_utf8_length( wc )) >= mMaxTextByteLength) + if ( (wstring_utf8_length( getWText() ) + wchar_utf8_length( wc )) > mMaxTextByteLength) { make_ui_sound("UISndBadKeystroke"); return 0; @@ -1547,12 +1043,26 @@ S32 LLTextEditor::addChar(S32 pos, llwchar wc) if (mLastCmd && mLastCmd->canExtend(pos)) { S32 delta = 0; + if (mPrevalidateFunc) + { + // get a copy of current text contents + LLWString test_string(getViewModel()->getDisplay()); + + // modify text contents as if this addChar succeeded + llassert(pos <= (S32)test_string.size()); + test_string.insert(pos, 1, wc); + if (!mPrevalidateFunc( test_string)) + { + return 0; + } + } mLastCmd->extendAndExecute(this, pos, wc, &delta); + return delta; } else { - return execute(new LLTextCmdAddChar(pos, FALSE, wc)); + return execute(new TextCmdAddChar(pos, FALSE, wc, LLTextSegmentPtr())); } } @@ -1573,6 +1083,28 @@ void LLTextEditor::addChar(llwchar wc) setCursorPos(mCursorPos + addChar( mCursorPos, wc )); } +void LLTextEditor::addLineBreakChar() +{ + if( !getEnabled() ) + { + return; + } + if( hasSelection() ) + { + deleteSelection(TRUE); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + removeChar(mCursorPos); + } + + LLStyleConstSP sp(new LLStyle(LLStyle::Params())); + LLTextSegmentPtr segment = new LLLineBreakTextSegment(sp, mCursorPos); + + S32 pos = execute(new TextCmdAddChar(mCursorPos, FALSE, '\n', segment)); + + setCursorPos(mCursorPos + pos); +} BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) @@ -1589,10 +1121,10 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) if( 0 < mCursorPos ) { startSelection(); - mCursorPos--; + setCursorPos(mCursorPos - 1); if( mask & MASK_CONTROL ) { - mCursorPos = prevWordPos(mCursorPos); + setCursorPos(prevWordPos(mCursorPos)); } mSelectionEnd = mCursorPos; } @@ -1602,10 +1134,10 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) if( mCursorPos < getLength() ) { startSelection(); - mCursorPos++; + setCursorPos(mCursorPos + 1); if( mask & MASK_CONTROL ) { - mCursorPos = nextWordPos(mCursorPos); + setCursorPos(nextWordPos(mCursorPos)); } mSelectionEnd = mCursorPos; } @@ -1627,7 +1159,7 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) startSelection(); if( mask & MASK_CONTROL ) { - mCursorPos = 0; + setCursorPos(0); } else { @@ -1652,7 +1184,7 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) startSelection(); if( mask & MASK_CONTROL ) { - mCursorPos = getLength(); + setCursorPos(getLength()); } else { @@ -1667,22 +1199,6 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) } } - if( !handled && mHandleEditKeysDirectly ) - { - if( (MASK_CONTROL & mask) && ('A' == key) ) - { - if( canSelectAll() ) - { - selectAll(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - if( handled ) { // take selection to 'primary' clipboard @@ -1703,14 +1219,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) switch( key ) { case KEY_UP: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() - 1); - } - else - { - changeLine( -1 ); - } + changeLine( -1 ); break; case KEY_PAGE_UP: @@ -1718,25 +1227,12 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) break; case KEY_HOME: - if (mReadOnly) - { - mScrollbar->setDocPos(0); - } - else - { - startOfLine(); - } + startOfLine(); break; case KEY_DOWN: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() + 1); - } - else - { - changeLine( 1 ); - } + changeLine( 1 ); + deselect(); break; case KEY_PAGE_DOWN: @@ -1744,24 +1240,13 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) break; case KEY_END: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPosMax()); - } - else - { - endOfLine(); - } + endOfLine(); break; case KEY_LEFT: - if (mReadOnly) - { - break; - } if( hasSelection() ) { - setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd )); + setCursorPos(llmin( mSelectionStart, mSelectionEnd )); } else { @@ -1771,19 +1256,15 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } break; case KEY_RIGHT: - if (mReadOnly) - { - break; - } if( hasSelection() ) { - setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd )); + setCursorPos(llmax( mSelectionStart, mSelectionEnd )); } else { @@ -1793,7 +1274,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } break; @@ -1804,10 +1285,11 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) + if (handled) { - mOnScrollEndCallback(mOnScrollEndData); + deselect(); } + return handled; } @@ -1843,7 +1325,7 @@ void LLTextEditor::cut() gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID ); deleteSelection( FALSE ); - needsReflow(); + onKeyStroke(); } BOOL LLTextEditor::canCopy() const @@ -1932,7 +1414,7 @@ void LLTextEditor::pasteHelper(bool is_primary) for( S32 i = 0; i < len; i++ ) { llwchar wc = clean_string[i]; - if( (wc < LLFont::FIRST_CHAR) && (wc != LF) ) + if( (wc < LLFontFreetype::FIRST_CHAR) && (wc != LF) ) { clean_string[i] = LL_UNKNOWN_CHAR; } @@ -1944,10 +1426,30 @@ void LLTextEditor::pasteHelper(bool is_primary) } // Insert the new text into the existing text. - setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE)); + + //paste text with linebreaks. + std::basic_string<llwchar>::size_type start = 0; + std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start); + + while(pos!=-1) + { + if(pos!=start) + { + std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,pos-start); + setCursorPos(mCursorPos + insert(mCursorPos, str, FALSE, LLTextSegmentPtr())); + } + addLineBreakChar(); + + start = pos+1; + pos = clean_string.find('\n',start); + } + + std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,clean_string.length()-start); + setCursorPos(mCursorPos + insert(mCursorPos, str, FALSE, LLTextSegmentPtr())); + deselect(); - needsReflow(); + onKeyStroke(); } @@ -1991,7 +1493,7 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) if( mask & MASK_SHIFT ) { startSelection(); - mCursorPos = 0; + setCursorPos(0); mSelectionEnd = mCursorPos; } else @@ -1999,7 +1501,7 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down // all move the cursor as if clicking, so should deselect. deselect(); - setCursorPos(0); + startOfDoc(); } break; @@ -2060,75 +1562,13 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) return handled; } -BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask) -{ - BOOL handled = FALSE; - // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. - if( KEY_DELETE == key ) - { - if( canDoDelete() ) - { - doDelete(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( MASK_CONTROL & mask ) +BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask) { - if( 'C' == key ) - { - if( canCopy() ) - { - copy(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'V' == key ) - { - if( canPaste() ) - { - paste(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'X' == key ) - { - if( canCut() ) - { - cut(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - - return handled; -} - - -BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit) -{ - *return_key_hit = FALSE; BOOL handled = TRUE; + if (mReadOnly) return FALSE; + switch( key ) { case KEY_INSERT: @@ -2150,7 +1590,7 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } break; @@ -2188,8 +1628,7 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return deleteSelection(FALSE); } - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); for( S32 i=0; i < spaces_needed; i++ ) @@ -2204,6 +1643,10 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return break; } + if (handled) + { + onKeyStroke(); + } return handled; } @@ -2224,88 +1667,32 @@ void LLTextEditor::unindentLineBeforeCloseBrace() BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) { BOOL handled = FALSE; - BOOL selection_modified = FALSE; - BOOL return_key_hit = FALSE; - BOOL text_may_have_changed = TRUE; - if ( gFocusMgr.getKeyboardFocus() == this ) + // Special case for TAB. If want to move to next field, report + // not handled and let the parent take care of field movement. + if (KEY_TAB == key && mTabsToNextField) { - // Special case for TAB. If want to move to next field, report - // not handled and let the parent take care of field movement. - if (KEY_TAB == key && mTabsToNextField) - { - return FALSE; - } - - handled = handleNavigationKey( key, mask ); - if( handled ) - { - text_may_have_changed = FALSE; - } - - if( !handled ) - { - handled = handleSelectionKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } - } - - if( !handled ) - { - handled = handleControlKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } - } - - if( !handled && mHandleEditKeysDirectly ) - { - handled = handleEditKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - text_may_have_changed = TRUE; - } - } - - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) - { - if( !handled ) - { - handled = handleSpecialKey( key, mask, &return_key_hit ); - if( handled ) - { - selection_modified = TRUE; - text_may_have_changed = TRUE; - } - } - + return FALSE; + } + + if (mReadOnly && mScroller) + { + handled = (mScroller && mScroller->handleKeyHere( key, mask )) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask); } - - if( handled ) + else { - resetKeystrokeTimer(); - - // Most keystrokes will make the selection box go away, but not all will. - if( !selection_modified && - KEY_SHIFT != key && - KEY_CONTROL != key && - KEY_ALT != key && - KEY_CAPSLOCK ) - { - deselect(); - } + handled = handleNavigationKey( key, mask ) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask) + || handleSpecialKey(key, mask); + } - if(text_may_have_changed) - { - needsReflow(); - } - needsScroll(); - } + if( handled ) + { + resetCursorBlink(); + needsScroll(); } return handled; @@ -2321,34 +1708,31 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) BOOL handled = FALSE; - if ( gFocusMgr.getKeyboardFocus() == this ) + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) { - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) + if( '}' == uni_char ) { - if( '}' == uni_char ) - { - unindentLineBeforeCloseBrace(); - } + unindentLineBeforeCloseBrace(); + } - // TODO: KLW Add auto show of tool tip on ( - addChar( uni_char ); + // TODO: KLW Add auto show of tool tip on ( + addChar( uni_char ); - // Keys that add characters temporarily hide the cursor - getWindow()->hideCursorUntilMouseMove(); + // Keys that add characters temporarily hide the cursor + getWindow()->hideCursorUntilMouseMove(); - handled = TRUE; - } + handled = TRUE; + } - if( handled ) - { - resetKeystrokeTimer(); + if( handled ) + { + resetCursorBlink(); - // Most keystrokes will make the selection box go away, but not all will. - deselect(); + // Most keystrokes will make the selection box go away, but not all will. + deselect(); - needsReflow(); - } + onKeyStroke(); } return handled; @@ -2380,8 +1764,7 @@ void LLTextEditor::doDelete() if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) { // Try to remove a full tab's worth of spaces - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); if( chars_to_remove == 0 ) { @@ -2403,9 +1786,10 @@ void LLTextEditor::doDelete() setCursorPos(mCursorPos + 1); removeChar(); } + } - needsReflow(); + onKeyStroke(); } //---------------------------------------------------------------------------- @@ -2448,7 +1832,7 @@ void LLTextEditor::undo() setCursorPos(pos); - needsReflow(); + onKeyStroke(); } BOOL LLTextEditor::canRedo() const @@ -2490,12 +1874,12 @@ void LLTextEditor::redo() setCursorPos(pos); - needsReflow(); + onKeyStroke(); } void LLTextEditor::onFocusReceived() { - LLUICtrl::onFocusReceived(); + LLTextBase::onFocusReceived(); updateAllowingLanguageInput(); } @@ -2518,323 +1902,57 @@ void LLTextEditor::onFocusLost() // Make sure cursor is shown again getWindow()->showCursorFromMouseMove(); - LLUICtrl::onFocusLost(); + LLTextBase::onFocusLost(); +} + +void LLTextEditor::onCommit() +{ + setControlValue(getValue()); + LLTextBase::onCommit(); } void LLTextEditor::setEnabled(BOOL enabled) { // just treat enabled as read-only flag - BOOL read_only = !enabled; + bool read_only = !enabled; if (read_only != mReadOnly) { - mReadOnly = read_only; + //mReadOnly = read_only; + LLTextBase::setReadOnly(read_only); updateSegments(); updateAllowingLanguageInput(); } } -void LLTextEditor::drawBackground() -{ - S32 left = 0; - S32 top = getRect().getHeight(); - S32 right = getRect().getWidth(); - S32 bottom = 0; - - LLColor4 bg_color = mReadOnly ? mReadOnlyBgColor.get() - : gFocusMgr.getKeyboardFocus() == this ? mFocusBgColor.get() : mWriteableBgColor.get(); - if( mShowLineNumbers ) { - gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only - gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, right, bottom, bg_color); // body text area to the right of line numbers - gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator - } else { - gl_rect_2d(left, top, right, bottom, bg_color); // body text area - } - - LLView::draw(); -} - -// Draws the black box behind the selected text -void LLTextEditor::drawSelectionBackground() +void LLTextEditor::showContextMenu(S32 x, S32 y) { - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection() ) + if (!mContextMenu) { - LLWString text = getWText(); - const S32 text_len = getLength(); - std::queue<S32> line_endings; - - S32 line_height = llround( mGLFont->getLineHeight() ); - - S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); - S32 selection_left_x = mTextRect.mLeft; - S32 selection_left_y = mTextRect.mTop - line_height; - S32 selection_right_x = mTextRect.mRight; - S32 selection_right_y = mTextRect.mBottom; - - BOOL selection_left_visible = FALSE; - BOOL selection_right_visible = FALSE; - - // Skip through the lines we aren't drawing. - S32 cur_line = mScrollbar->getDocPos(); - - S32 left_line_num = cur_line; - S32 num_lines = getLineCount(); - S32 right_line_num = num_lines - 1; - - S32 line_start = -1; - if (cur_line >= num_lines) - { - return; - } - - line_start = getLineStart(cur_line); - - S32 left_visible_pos = line_start; - S32 right_visible_pos = line_start; - - S32 text_y = mTextRect.mTop - line_height; - - // Find the coordinates of the selected area - while((cur_line < num_lines)) - { - S32 next_line = -1; - S32 line_end = text_len; - - if ((cur_line + 1) < num_lines) - { - next_line = getLineStart(cur_line + 1); - line_end = next_line; - - line_end = ( (line_end - line_start)==0 || text[next_line-1] == '\n' || text[next_line-1] == '\0' || text[next_line-1] == ' ' || text[next_line-1] == '\t' ) ? next_line-1 : next_line; - } - - const llwchar* line = text.c_str() + line_start; - - if( line_start <= selection_left && selection_left <= line_end ) - { - left_line_num = cur_line; - selection_left_visible = TRUE; - selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems); - selection_left_y = text_y; - } - if( line_start <= selection_right && selection_right <= line_end ) - { - right_line_num = cur_line; - selection_right_visible = TRUE; - selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems); - if (selection_right == line_end) - { - // add empty space for "newline" - //selection_right_x += mGLFont->getWidth("n"); - } - selection_right_y = text_y; - } - - // if selection spans end of current line... - if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right) - { - // extend selection slightly beyond end of line - // to indicate selection of newline character (use "n" character to determine width) - const LLWString nstr(utf8str_to_wstring(std::string("n"))); - line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str())); - } - - // move down one line - text_y -= line_height; - - right_visible_pos = line_end; - line_start = next_line; - cur_line++; - - if (selection_right_visible) - { - break; - } - } - - // Draw the selection box (we're using a box instead of reversing the colors on the selected text). - BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos); - if( selection_visible ) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mReadOnly ? mReadOnlyBgColor.get() : mWriteableBgColor.get(); - F32 alpha = hasFocus() ? 1.f : 0.5f; - gGL.color4f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], alpha ); - S32 margin_offset = mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0; - - if( selection_left_y == selection_right_y ) - { - // Draw from selection start to selection end - gl_rect_2d( selection_left_x + margin_offset, selection_left_y + line_height + 1, - selection_right_x + margin_offset, selection_right_y); - } - else - { - // Draw from selection start to the end of the first line - if( mTextRect.mRight == selection_left_x ) - { - selection_left_x -= CURSOR_THICKNESS; - } - - S32 line_end = line_endings.front(); - line_endings.pop(); - gl_rect_2d( selection_left_x + margin_offset, selection_left_y + line_height + 1, - line_end + margin_offset, selection_left_y ); - - S32 line_num = left_line_num + 1; - while(line_endings.size()) - { - S32 vert_offset = -(line_num - left_line_num) * line_height; - // Draw the block between the two lines - gl_rect_2d( mTextRect.mLeft + margin_offset, selection_left_y + vert_offset + line_height + 1, - line_endings.front() + margin_offset, selection_left_y + vert_offset); - line_endings.pop(); - line_num++; - } - - // Draw from the start of the last line to selection end - if( mTextRect.mLeft == selection_right_x ) - { - selection_right_x += CURSOR_THICKNESS; - } - gl_rect_2d( mTextRect.mLeft + margin_offset, selection_right_y + line_height + 1, - selection_right_x + margin_offset, selection_right_y ); - } - } + mContextMenu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>("menu_text_editor.xml", + LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); } -} - -void LLTextEditor::drawCursor() -{ - if( gFocusMgr.getKeyboardFocus() == this - && gShowTextEditCursor && !mReadOnly) - { - LLWString text = getWText(); - const S32 text_len = getLength(); - - // Skip through the lines we aren't drawing. - S32 cur_pos = mScrollbar->getDocPos(); - S32 num_lines = getLineCount(); - if (cur_pos >= num_lines) - { - return; - } - S32 line_start = getLineStart(cur_pos); - - F32 line_height = mGLFont->getLineHeight(); - F32 text_y = (F32)(mTextRect.mTop) - line_height; - - F32 cursor_left = 0.f; - F32 next_char_left = 0.f; - F32 cursor_bottom = 0.f; - BOOL cursor_visible = FALSE; - - S32 line_end = 0; - // Determine if the cursor is visible and if so what its coordinates are. - while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines)) - { - line_end = text_len + 1; - S32 next_line = -1; - - if ((cur_pos + 1) < num_lines) - { - next_line = getLineStart(cur_pos + 1); - line_end = next_line - 1; - } - - const llwchar* line = text.c_str() + line_start; - - // Find the cursor and selection bounds - if( line_start <= mCursorPos && mCursorPos <= line_end ) - { - cursor_visible = TRUE; - next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems ); - cursor_left = next_char_left - 1.f; - cursor_bottom = text_y; - break; - } - - // move down one line - text_y -= line_height; - line_start = next_line; - cur_pos++; - } - - if(mShowLineNumbers) - { - cursor_left += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - } - - // Draw the cursor - if( cursor_visible ) - { - // (Flash the cursor every half second starting a fixed time after the last keystroke) - F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); - if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) - { - F32 cursor_top = cursor_bottom + line_height + 1.f; - F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS; - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - cursor_left += CURSOR_THICKNESS; - const LLWString space(utf8str_to_wstring(std::string(" "))); - F32 spacew = mGLFont->getWidthF32(space.c_str()); - if (mCursorPos == line_end) - { - cursor_right = cursor_left + spacew; - } - else - { - F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems); - cursor_right = cursor_left + llmax(spacew, width); - } - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + // Route menu to this class + // previously this was done in ::handleRightMoseDown: + //if(hasTabStop()) + // setFocus(TRUE) - why? weird... + // and then inside setFocus + // .... + // gEditMenuHandler = this; + // .... + // but this didn't work in all cases and just weird... + //why not here? + // (all this was done for EXT-4443) - gGL.color4fv( mCursorColor.get().mV ); - - gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top), - llfloor(cursor_right), llfloor(cursor_bottom)); + gEditMenuHandler = this; - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') - { - const LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos); - LLColor4 text_color; - if (segmentp) - { - text_color = segmentp->getColor(); - } - else if (mReadOnly) - { - text_color = mReadOnlyFgColor.get(); - } - else - { - text_color = mFgColor.get(); - } - mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height, - LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f), - LLFontGL::LEFT, LLFontGL::TOP, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - 1); - } - - // Make sure the IME is in the right place - LLRect screen_pos = calcScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_left), screen_pos.mBottom + llfloor(cursor_top) ); - - ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]); - ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); - getWindow()->setLanguageTextInput( ime_pos ); - } - } - } + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + mContextMenu->show(screen_x, screen_y); } + void LLTextEditor::drawPreeditMarker() { static LLUICachedControl<F32> preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); @@ -2856,17 +1974,17 @@ void LLTextEditor::drawPreeditMarker() const S32 text_len = getLength(); const S32 num_lines = getLineCount(); - S32 cur_line = mScrollbar->getDocPos(); + S32 cur_line = getFirstVisibleLine(); if (cur_line >= num_lines) { return; } - const S32 line_height = llround( mGLFont->getLineHeight() ); + const S32 line_height = llround( mDefaultFont->getLineHeight() ); S32 line_start = getLineStart(cur_line); - S32 line_y = mTextRect.mTop - line_height; - while((mTextRect.mBottom <= line_y) && (num_lines > cur_line)) + S32 line_y = mVisibleTextRect.mTop - line_height; + while((mVisibleTextRect.mBottom <= line_y) && (num_lines > cur_line)) { S32 next_start = -1; S32 line_end = text_len; @@ -2898,19 +2016,19 @@ void LLTextEditor::drawPreeditMarker() continue; } - S32 preedit_left = mTextRect.mLeft; + S32 preedit_left = mVisibleTextRect.mLeft; if (left > line_start) { - preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems); + preedit_left += mDefaultFont->getWidth(text, line_start, left - line_start); } - S32 preedit_right = mTextRect.mLeft; + S32 preedit_right = mVisibleTextRect.mLeft; if (right < line_end) { - preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, line_start, right - line_start); } else { - preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, line_start, line_end - line_start); } if (mPreeditStandouts[i]) @@ -2940,251 +2058,85 @@ void LLTextEditor::drawPreeditMarker() } -void LLTextEditor::drawText() +void LLTextEditor::drawLineNumbers() { - LLWString text = getWText(); - const S32 text_len = getLength(); - if( text_len <= 0 ) - { - return; - } - S32 selection_left = -1; - S32 selection_right = -1; - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection()) - { - selection_left = llmin( mSelectionStart, mSelectionEnd ); - selection_right = llmax( mSelectionStart, mSelectionEnd ); - } - LLGLSUIDefault gls_ui; - - S32 cur_line = mScrollbar->getDocPos(); + LLRect scrolled_view_rect = getVisibleDocumentRect(); + LLRect content_rect = getVisibleTextRect(); + LLLocalClipRect clip(content_rect); + S32 first_line = getFirstVisibleLine(); S32 num_lines = getLineCount(); - if (cur_line >= num_lines) + if (first_line >= num_lines) { return; } - S32 line_start = getLineStart(cur_line); - LLTextSegment t(line_start); - segment_list_t::iterator seg_iter; - seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare()); - if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter; - LLTextSegment* cur_segment = *seg_iter; - - S32 line_height = llround( mGLFont->getLineHeight() ); - F32 text_y = (F32)(mTextRect.mTop - line_height); - while((mTextRect.mBottom <= text_y) && (cur_line < num_lines)) + S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum; + + if (mShowLineNumbers) { - S32 next_start = -1; - S32 line_end = text_len; + S32 left = 0; + S32 top = getRect().getHeight(); + S32 bottom = 0; - if ((cur_line + 1) < num_lines) - { - next_start = getLineStart(cur_line + 1); - line_end = next_start; - } - if ( text[line_end-1] == '\n' ) - { - --line_end; - } - - F32 text_x = (F32)mTextRect.mLeft; + gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only + gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator + + S32 last_line_num = -1; - S32 seg_start = line_start; - while( seg_start < line_end ) + for (S32 cur_line = first_line; cur_line < num_lines; cur_line++) { - while( cur_segment->getEnd() <= seg_start ) + line_info& line = mLineInfoList[cur_line]; + + if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom) { - seg_iter++; - if (seg_iter == mSegments.end()) - { - llwarns << "Ran off the segmentation end!" << llendl; - return; - } - cur_segment = *seg_iter; + break; } - - // Draw a segment within the line - S32 clipped_end = llmin( line_end, cur_segment->getEnd() ); - S32 clipped_len = clipped_end - seg_start; - if( clipped_len > 0 ) - { - LLStyleSP style = cur_segment->getStyle(); - if ( style->isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end)) - { - S32 style_image_height = style->mImageHeight; - S32 style_image_width = style->mImageWidth; - LLUIImagePtr image = style->getImage(); - image->draw(llround(text_x), llround(text_y)+line_height-style_image_height, - style_image_width, style_image_height); - } - - if (cur_segment == mHoverSegment && style->getIsEmbeddedItem()) - { - style->mUnderline = TRUE; - } - - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - - if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) && mIsSelecting && (mSelectionStart == mSelectionEnd) ) - { - mHTML = style->getLinkHREF(); - } - drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x ); - - // Note: text_x is incremented by drawClippedSegment() - seg_start += clipped_len; + S32 line_bottom = line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom; + // draw the line numbers + if(line.mLineNum != last_line_num && line.mRect.mTop <= scrolled_view_rect.mTop) + { + const LLFontGL *num_font = LLFontGL::getFontMonospace(); + const LLWString ltext = utf8str_to_wstring(llformat("%d", line.mLineNum )); + BOOL is_cur_line = cursor_line == line.mLineNum; + const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL; + const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor; + num_font->render( + ltext, // string to draw + 0, // begin offset + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2, // x + line_bottom, // y + fg_color, + LLFontGL::RIGHT, // horizontal alignment + LLFontGL::BOTTOM, // vertical alignment + style, + LLFontGL::NO_SHADOW, + S32_MAX, // max chars + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2); // max pixels + last_line_num = line.mLineNum; } } - - // move down one line - text_y -= (F32)line_height; - - line_start = next_start; - cur_line++; } } - -// Draws a single text segment, reversing the color for selection if needed. -void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyleSP& style, F32* right_x ) -{ - if (!style->isVisible()) - { - return; - } - - const LLFontGL* font = mGLFont; - - LLColor4 color = style->getColor(); - - if ( style->getFontString()[0] ) - { - font = style->getFont(); - } - - U8 font_flags = LLFontGL::NORMAL; - - if (style->mBold) - { - font_flags |= LLFontGL::BOLD; - } - if (style->mItalic) - { - font_flags |= LLFontGL::ITALIC; - } - if (style->mUnderline) - { - font_flags |= LLFontGL::UNDERLINE; - } - - if (style->getIsEmbeddedItem()) - { - static LLUICachedControl<LLColor4> text_embedded_item_readonly_color ("TextEmbeddedItemReadOnlyColor", *(new LLColor4)); - static LLUICachedControl<LLColor4> text_embedded_item_color ("TextEmbeddedItemColor", *(new LLColor4)); - if (mReadOnly) - { - color = text_embedded_item_readonly_color; - } - else - { - color = text_embedded_item_color; - } - } - - F32 y_top = y + (F32)llround(font->getLineHeight()); - - if( selection_left > seg_start ) - { - // Draw normally - S32 start = seg_start; - S32 end = llmin( selection_left, seg_end ); - S32 length = end - start; - font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, mGLFontStyle, LLFontGL::NO_SHADOW, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - x = *right_x; - - if( (selection_left < seg_end) && (selection_right > seg_start) ) - { - // Draw reversed - S32 start = llmax( selection_left, seg_start ); - S32 end = llmin( selection_right, seg_end ); - S32 length = end - start; - - font->render(text, start, x, y_top, - LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), - LLFontGL::LEFT, LLFontGL::TOP, mGLFontStyle, LLFontGL::NO_SHADOW, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - x = *right_x; - if( selection_right < seg_end ) - { - // Draw normally - S32 start = llmax( selection_right, seg_start ); - S32 end = seg_end; - S32 length = end - start; - font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, mGLFontStyle, LLFontGL::NO_SHADOW, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - } - - void LLTextEditor::draw() { - // do on-demand reflow - if (mReflowNeeded) { - updateLineStartList(); - mReflowNeeded = FALSE; - } - - // then update scroll position, as cursor may have moved - if (mScrollNeeded) - { - updateScrollFromCursor(); - mScrollNeeded = FALSE; - } - - { - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); - LLLocalClipRect clip(LLRect(0, getRect().getHeight(), getRect().getWidth() - (mScrollbar->getVisible() ? scrollbar_size : 0), 0)); - - bindEmbeddedChars( mGLFont ); - - drawBackground(); - drawSelectionBackground(); + // pad clipping rectangle so that cursor can draw at full width + // when at left edge of mVisibleTextRect + LLRect clip_rect(mVisibleTextRect); + clip_rect.stretch(1); + LLLocalClipRect clip(clip_rect); drawPreeditMarker(); - drawText(); - drawCursor(); - - unbindEmbeddedChars( mGLFont ); - - //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret - // when in readonly mode - mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly); } - - LLView::draw(); // Draw children (scrollbar and border) - - // remember if we are supposed to be at the bottom of the buffer - mScrolledToBottom = isScrolledToBottom(); -} + LLTextBase::draw(); + drawLineNumbers(); -void LLTextEditor::onTabInto() -{ - // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character - // theoretically, one could selectAll if mTabsToNextField is true, but we couldn't think of a use case - // where you'd want to select all anyway - // preserve insertion point when returning to the editor - //selectAll(); -} - -// virtual -void LLTextEditor::clear() -{ - setText(LLStringUtil::null); + //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret + // when in readonly mode + mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly); } // Start or stop the editor from accepting text-editing keystrokes @@ -3202,7 +2154,7 @@ void LLTextEditor::setFocus( BOOL new_state ) getWindow()->allowLanguageTextInput(this, FALSE); } - LLUICtrl::setFocus( new_state ); + LLTextBase::setFocus( new_state ); if( new_state ) { @@ -3210,7 +2162,7 @@ void LLTextEditor::setFocus( BOOL new_state ) gEditMenuHandler = this; // Don't start the cursor flashing right away - resetKeystrokeTimer(); + resetCursorBlink(); } else { @@ -3224,286 +2176,23 @@ void LLTextEditor::setFocus( BOOL new_state ) } } -// virtual -BOOL LLTextEditor::acceptsTextInput() const -{ - return !mReadOnly; -} - -// Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text. -S32 LLTextEditor::getPos( S32 line, S32 offset ) -{ - S32 line_start = getLineStart(line); - S32 next_start = getLineStart(line+1); - if (next_start == line_start) - { - next_start = getLength() + 1; - } - S32 line_length = next_start - line_start - 1; - line_length = llmax(line_length, 0); - return line_start + llmin( offset, line_length ); -} - - -void LLTextEditor::changePage( S32 delta ) -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - // get desired x position to remember previous position - S32 desired_x_pixel = mDesiredXPixel; - - // allow one line overlap - S32 page_size = mScrollbar->getPageSize() - 1; - if( delta == -1 ) - { - line = llmax( line - page_size, 0); - setCursorPos(getPos( line, offset )); - mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size ); - } - else - if( delta == 1 ) - { - setCursorPos(getPos( line + page_size, offset )); - mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size ); - } - - // put desired position into remember-buffer after setCursorPos() - mDesiredXPixel = desired_x_pixel; - - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -void LLTextEditor::changeLine( S32 delta ) -{ - bindEmbeddedChars(mGLFont); - - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - S32 line_start = getLineStart(line); - - // set desired x position to remembered previous position - S32 desired_x_pixel = mDesiredXPixel; - // if remembered position was reset (thus -1), calculate new one here - if( desired_x_pixel == -1 ) - { - LLWString text(getWText()); - desired_x_pixel = mGLFont->getWidth(text.c_str(), line_start, offset, mAllowEmbeddedItems ); - } - - S32 new_line = 0; - if( (delta < 0) && (line > 0 ) ) - { - new_line = line - 1; - } - else - if( (delta > 0) && (line < (getLineCount() - 1)) ) - { - new_line = line + 1; - } - else - { - unbindEmbeddedChars(mGLFont); - return; - } - - S32 num_lines = getLineCount(); - S32 new_line_start = getLineStart(new_line); - S32 new_line_end = getLength(); - if (new_line + 1 < num_lines) - { - new_line_end = getLineStart(new_line + 1) - 1; - } - - S32 new_line_len = new_line_end - new_line_start; - - S32 new_offset; - LLWString text(getWText()); - new_offset = mGLFont->charFromPixelOffset(text.c_str(), new_line_start, - (F32)desired_x_pixel, - (F32)mTextRect.getWidth(), - new_line_len, - mAllowEmbeddedItems); - - setCursorPos (getPos( new_line, new_offset )); - - // put desired position into remember-buffer after setCursorPos() - mDesiredXPixel = desired_x_pixel; - unbindEmbeddedChars(mGLFont); -} - -BOOL LLTextEditor::isScrolledToTop() -{ - return mScrollbar->isAtBeginning(); -} - -BOOL LLTextEditor::isScrolledToBottom() -{ - return mScrollbar->isAtEnd(); -} - - -void LLTextEditor::startOfLine() -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - setCursorPos(mCursorPos - offset); -} - - // public void LLTextEditor::setCursorAndScrollToEnd() { deselect(); endOfDoc(); - needsScroll(); -} - -void LLTextEditor::getLineAndColumnForPosition( S32 position, S32* line, S32* col, BOOL include_wordwrap ) -{ - if( include_wordwrap ) - { - getLineAndOffset( mCursorPos, line, col ); - } - else - { - LLWString text = getWText(); - S32 line_count = 0; - S32 line_start = 0; - S32 i; - for( i = 0; text[i] && (i < position); i++ ) - { - if( '\n' == text[i] ) - { - line_start = i + 1; - line_count++; - } - } - *line = line_count; - *col = i - line_start; - } } void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ) { - getLineAndColumnForPosition(mCursorPos, line, col, include_wordwrap); -} - -S32 LLTextEditor::getCurrentLine() -{ - return getLineForPosition(mCursorPos); -} - -S32 LLTextEditor::getLineForPosition(S32 position) -{ - S32 line, col; - getLineAndColumnForPosition(position, &line, &col, FALSE); - return line; -} - - -void LLTextEditor::endOfLine() -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - S32 num_lines = getLineCount(); - if (line + 1 >= num_lines) - { - setCursorPos(getLength()); - } - else - { - setCursorPos( getLineStart(line + 1) - 1 ); - } -} - -void LLTextEditor::endOfDoc() -{ - mScrollbar->setDocPos(mScrollbar->getDocPosMax()); - mScrolledToBottom = true; - - S32 len = getLength(); - if( len ) - { - setCursorPos(len); - } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -// Sets the scrollbar from the cursor position -void LLTextEditor::updateScrollFromCursor() -{ - mScrollbar->setDocSize( getLineCount() ); - - if (mReadOnly) - { - // no cursor in read only mode - return; - } - - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - S32 page_size = mScrollbar->getPageSize(); - - if( line < mScrollbar->getDocPos() ) - { - // scroll so that the cursor is at the top of the page - mScrollbar->setDocPos( line ); - } - else if( line >= mScrollbar->getDocPos() + page_size - 1 ) - { - S32 new_pos = 0; - if( line < mScrollbar->getDocSize() - 1 ) - { - // scroll so that the cursor is one line above the bottom of the page, - new_pos = line - page_size + 1; - } - else - { - // if there is less than a page of text remaining, scroll so that the cursor is at the bottom - new_pos = mScrollbar->getDocPosMax(); - } - mScrollbar->setDocPos( new_pos ); - } - - // Check if we've scrolled to bottom for callback if asked for callback - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - LLView::reshape( width, height, called_from_parent ); - - // do this first after reshape, because other things depend on - // up-to-date mTextRect - updateTextRect(); - - needsReflow(); - - // propagate shape information to scrollbar - mScrollbar->setDocSize( getLineCount() ); - - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 page_lines = mTextRect.getHeight() / line_height; - mScrollbar->setPageSize( page_lines ); + *line = getLineNumFromDocIndex(mCursorPos, include_wordwrap); + *col = getLineOffsetFromDocIndex(mCursorPos, include_wordwrap); } void LLTextEditor::autoIndent() { // Count the number of spaces in the current line - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 line = getLineNumFromDocIndex(mCursorPos, false); S32 line_start = getLineStart(line); S32 space_count = 0; S32 i; @@ -3522,7 +2211,10 @@ void LLTextEditor::autoIndent() } // Insert that number of spaces on the new line - addChar( '\n' ); + + //appendLineBreakSegment(LLStyle::Params());//addChar( '\n' ); + addLineBreakChar(); + for( i = 0; i < space_count; i++ ) { addChar( ' ' ); @@ -3541,129 +2233,14 @@ void LLTextEditor::insertText(const std::string &new_text) deleteSelection(TRUE); } - setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE )); + setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE, LLTextSegmentPtr() )); - needsReflow(); - setEnabled( enabled ); } - -void LLTextEditor::appendColoredText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - const LLColor4 &color, - const std::string& font_name) -{ - LLColor4 lcolor=color; - if (mParseHighlights) - { - LLTextParser* highlight = LLTextParser::getInstance(); - highlight->parseFullLineHighlights(new_text, &lcolor); - } - - LLStyleSP style(new LLStyle); - style->setVisible(true); - style->setColor(lcolor); - style->setFontName(font_name); - appendStyledText(new_text, allow_undo, prepend_newline, style); -} - -void LLTextEditor::appendStyledText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - LLStyleSP stylep) -{ - S32 part = (S32)LLTextParser::WHOLE; - if(mParseHTML) - { - - S32 start=0,end=0; - std::string text = new_text; - while ( findHTML(text, &start, &end) ) - { - LLStyleSP html(new LLStyle); - html->setVisible(true); - html->setColor(mLinkColor); - if (stylep) - { - html->setFontName(stylep->getFontString()); - } - html->mUnderline = TRUE; - - if (start > 0) - { - if (part == (S32)LLTextParser::WHOLE || - part == (S32)LLTextParser::START) - { - part = (S32)LLTextParser::START; - } - else - { - part = (S32)LLTextParser::MIDDLE; - } - std::string subtext=text.substr(0,start); - appendHighlightedText(subtext,allow_undo, prepend_newline, part, stylep); - } - - html->setLinkHREF(text.substr(start,end-start)); - appendText(text.substr(start, end-start),allow_undo, prepend_newline, html); - if (end < (S32)text.length()) - { - text = text.substr(end,text.length() - end); - end=0; - part=(S32)LLTextParser::END; - } - else - { - break; - } - } - if (part != (S32)LLTextParser::WHOLE) part=(S32)LLTextParser::END; - if (end < (S32)text.length()) appendHighlightedText(text,allow_undo, prepend_newline, part, stylep); - } - else - { - appendHighlightedText(new_text, allow_undo, prepend_newline, part, stylep); - } -} - -void LLTextEditor::appendHighlightedText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - S32 highlight_part, - LLStyleSP stylep) -{ - if (mParseHighlights) - { - LLTextParser* highlight = LLTextParser::getInstance(); - - if (highlight && stylep) - { - LLSD pieces = highlight->parsePartialLineHighlights(new_text, stylep->getColor(), highlight_part); - bool lprepend=prepend_newline; - for (S32 i=0;i<pieces.size();i++) - { - LLSD color_llsd = pieces[i]["color"]; - LLColor4 lcolor; - lcolor.setValue(color_llsd); - LLStyleSP lstylep(new LLStyle(*stylep)); - lstylep->setColor(lcolor); - if (i != 0 && (pieces.size() > 1) ) lprepend=FALSE; - appendText((std::string)pieces[i]["text"], allow_undo, lprepend, lstylep); - } - return; - } - } - appendText(new_text, allow_undo, prepend_newline, stylep); -} - -// Appends new text to end of document -void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool prepend_newline, - const LLStyleSP stylep) +void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) { // Save old state - BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); S32 selection_start = mSelectionStart; S32 selection_end = mSelectionEnd; BOOL was_selecting = mIsSelecting; @@ -3675,50 +2252,17 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool setCursorPos(old_length); - // Add carriage return if not first line - if (getLength() != 0 - && prepend_newline) - { - std::string final_text = "\n"; - final_text += new_text; - append(utf8str_to_wstring(final_text), TRUE); - } - else - { - append(utf8str_to_wstring(new_text), TRUE ); - } + LLWString widget_wide_text = utf8str_to_wstring(text); + + LLTextSegmentPtr segment = new LLInlineViewSegment(params, old_length, old_length + widget_wide_text.size()); + insert(getLength(), widget_wide_text, FALSE, segment); - if (stylep) - { - S32 segment_start = old_length; - S32 segment_end = getLength(); - LLTextSegment* segment = new LLTextSegment(stylep, segment_start, segment_end ); - mSegments.push_back(segment); - } - - needsReflow(); - // Set the cursor and scroll position - // Maintain the scroll position unless the scroll was at the end of the doc (in which - // case, move it to the new end of the doc) or unless the user was doing actively selecting - if( was_scrolled_to_bottom && !was_selecting ) - { - if( selection_start != selection_end ) - { - // maintain an existing non-active selection - mSelectionStart = selection_start; - mSelectionEnd = selection_end; - } - endOfDoc(); - } - else if( selection_start != selection_end ) + if( selection_start != selection_end ) { mSelectionStart = selection_start; mSelectionEnd = selection_end; - - - mIsSelecting = was_selecting; setCursorPos(cursor_pos); } @@ -3731,7 +2275,7 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool setCursorPos(cursor_pos); } - if( !allow_undo ) + if (!allow_undo) { blockUndo(); } @@ -3744,59 +2288,13 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) remove(getLength() - num_chars, num_chars, FALSE); S32 len = getLength(); - mCursorPos = llclamp(mCursorPos, 0, len); + setCursorPos (llclamp(mCursorPos, 0, len)); mSelectionStart = llclamp(mSelectionStart, 0, len); mSelectionEnd = llclamp(mSelectionEnd, 0, len); - pruneSegments(); - - // pruneSegments will invalidate mLineStartList. - updateLineStartList(); needsScroll(); } -/////////////////////////////////////////////////////////////////// -// Returns change in number of characters in mWText - -S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) -{ - LLWString text(getWText()); - S32 old_len = text.length(); // length() returns character length - S32 insert_len = wstr.length(); - - text.insert(pos, wstr); - getViewModel()->setDisplay(text); - - if ( truncate() ) - { - // The user's not getting everything he's hoping for - make_ui_sound("UISndBadKeystroke"); - insert_len = getLength() - old_len; - } - - return insert_len; -} - -S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) -{ - LLWString text(getWText()); - text.erase(pos, length); - getViewModel()->setDisplay(text); - return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length -} - -S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc) -{ - if (pos > (S32)getLength()) - { - return 0; - } - LLWString text(getWText()); - text[pos] = wc; - getViewModel()->setDisplay(text); - return 1; -} - //---------------------------------------------------------------------------- void LLTextEditor::makePristine() @@ -3852,33 +2350,19 @@ BOOL LLTextEditor::tryToRevertToPristineState() i--; } } - - needsReflow(); } return isPristine(); // TRUE => success } -void LLTextEditor::updateTextRect() -{ - static LLUICachedControl<S32> texteditor_border ("UITextEditorBorder", 0); - static LLUICachedControl<S32> texteditor_h_pad ("UITextEditorHPad", 0); - static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); - static LLUICachedControl<S32> texteditor_vpad_top ("UITextEditorVPadTop", 0); - - mTextRect.setOriginAndSize( - texteditor_border + texteditor_h_pad, - texteditor_border, - getRect().getWidth() - scrollbar_size - 2 * (texteditor_border + texteditor_h_pad), - getRect().getHeight() - 2 * texteditor_border - texteditor_vpad_top ); -} - +static LLFastTimer::DeclareTimer FTM_SYNTAX_HIGHLIGHTING("Syntax Highlighting"); void LLTextEditor::loadKeywords(const std::string& filename, const std::vector<std::string>& funcs, const std::vector<std::string>& tooltips, const LLColor3& color) { + LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); if(mKeywords.loadFromFile(filename)) { S32 count = llmin(funcs.size(), tooltips.size()); @@ -3887,199 +2371,73 @@ void LLTextEditor::loadKeywords(const std::string& filename, std::string name = utf8str_trim(funcs[i]); mKeywords.addToken(LLKeywordToken::WORD, name, color, tooltips[i] ); } + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this); - mKeywords.findSegments( &mSegments, getWText(), mDefaultColor.get() ); - - llassert( mSegments.front()->getStart() == 0 ); - llassert( mSegments.back()->getEnd() == getLength() ); + mSegments.clear(); + segment_set_t::iterator insert_it = mSegments.begin(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) + { + insert_it = mSegments.insert(insert_it, *list_it); + } } } void LLTextEditor::updateSegments() { - if (mKeywords.isLoaded()) + if (mReflowIndex < S32_MAX && mKeywords.isLoaded()) { + LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); // HACK: No non-ascii keywords for now - mKeywords.findSegments(&mSegments, getWText(), mDefaultColor.get()); - } - else if (mAllowEmbeddedItems) - { - findEmbeddedItemSegments(); - } - - // Make sure we have at least one segment - if (mSegments.size() == 1 && mSegments[0]->getIsDefault()) - { - delete mSegments[0]; - mSegments.clear(); // create default segment - } - if (mSegments.empty()) - { - LLColor4 text_color = ( mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get() ); - LLTextSegment* default_segment = new LLTextSegment( text_color, 0, getLength() ); - default_segment->setIsDefault(TRUE); - mSegments.push_back(default_segment); - } -} + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this); -// Only effective if text was removed from the end of the editor -// *NOTE: Using this will invalidate references to mSegments from mLineStartList. -void LLTextEditor::pruneSegments() -{ - S32 len = getLength(); - // Find and update the first valid segment - segment_list_t::iterator iter = mSegments.end(); - while(iter != mSegments.begin()) - { - --iter; - LLTextSegment* seg = *iter; - if (seg->getStart() < len) + clearSegments(); + segment_set_t::iterator insert_it = mSegments.begin(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) { - // valid segment - if (seg->getEnd() > len) - { - seg->setEnd(len); - } - break; // done - } - } - if (iter != mSegments.end()) - { - // erase invalid segments - ++iter; - std::for_each(iter, mSegments.end(), DeletePointer()); - mSegments.erase(iter, mSegments.end()); - } - else - { - llwarns << "Tried to erase end of empty LLTextEditor" << llendl; - } -} - -void LLTextEditor::findEmbeddedItemSegments() -{ - mHoverSegment = NULL; - std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); - mSegments.clear(); - - BOOL found_embedded_items = FALSE; - LLWString text = getWText(); - S32 idx = 0; - while( text[idx] ) - { - if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) - { - found_embedded_items = TRUE; - break; + insertSegment(*list_it); } - ++idx; } - if( !found_embedded_items ) - { - return; - } - - S32 text_len = text.length(); - - BOOL in_text = FALSE; - - LLColor4 text_color = ( mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get() ); - - if( idx > 0 ) - { - mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text - in_text = TRUE; - } - - LLStyleSP embedded_style(new LLStyle); - embedded_style->setIsEmbeddedItem( TRUE ); - - // Start with i just after the first embedded item - while ( text[idx] ) - { - if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) - { - if( in_text ) - { - mSegments.back()->setEnd( idx ); - } - mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) ); // item - in_text = FALSE; - } - else - if( !in_text ) - { - mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) ); // text - in_text = TRUE; - } - ++idx; - } + LLTextBase::updateSegments(); } -BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask) +void LLTextEditor::updateLinkSegments() { - if ( hasMouseCapture() ) + LLWString wtext = getWText(); + + // update any segments that contain a link + for (segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); ++it) { - // This mouse up was part of a click. - // Regardless of where the cursor is, see if we recently touched a link - // and launch it if we did. - if (mParseHTML && mHTML.length() > 0) + LLTextSegment *segment = *it; + if (segment && segment->getStyle() && segment->getStyle()->isLink()) { - //Special handling for slurls - if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) ) + // if the link's label (what the user can edit) is a valid Url, + // then update the link's HREF to be the same as the label text. + // This lets users edit Urls in-place. + LLStyleConstSP style = segment->getStyle(); + LLStyleSP new_style(new LLStyle(*style)); + LLWString url_label = wtext.substr(segment->getStart(), segment->getEnd()-segment->getStart()); + if (LLUrlRegistry::instance().hasUrl(url_label)) { - if (mURLcallback!=NULL) (*mURLcallback)(mHTML); + std::string new_url = wstring_to_utf8str(url_label); + LLStringUtil::trim(new_url); + new_style->setLinkHREF(new_url); + LLStyleConstSP sp(new_style); + segment->setStyle(sp); } - mHTML.clear(); } } - - return FALSE; } -// Finds the text segment (if any) at the give local screen position -const LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y ) const -{ - // Find the cursor position at the requested local screen position - S32 offset = getCursorPosFromLocalCoord( x, y, FALSE ); - S32 idx = getSegmentIdxAtOffset(offset); - return idx >= 0 ? mSegments[idx] : NULL; -} - -const LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset) const -{ - S32 idx = getSegmentIdxAtOffset(offset); - return idx >= 0 ? mSegments[idx] : NULL; -} - -S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset) const -{ - if (mSegments.empty() || offset < 0 || offset >= getLength()) - { - return -1; - } - else - { - S32 segidx, segoff; - getSegmentAndOffset(offset, &segidx, &segoff); - return segidx; - } -} void LLTextEditor::onMouseCaptureLost() { endSelection(); } -void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata) -{ - mOnScrollEndCallback = callback; - mOnScrollEndData = userdata; - mScrollbar->setOnScrollEndCallback(callback, userdata); -} - /////////////////////////////////////////////////////////////////// // Hack for Notecards @@ -4163,10 +2521,9 @@ BOOL LLTextEditor::importBuffer(const char* buffer, S32 length ) delete[] text; - setCursorPos(0); + startOfDoc(); deselect(); - needsReflow(); return success; } @@ -4184,237 +2541,6 @@ BOOL LLTextEditor::exportBuffer(std::string &buffer ) return TRUE; } -////////////////////////////////////////////////////////////////////////// -// LLTextSegment - -LLTextSegment::LLTextSegment(S32 start) : - mStart(start), - mEnd(0), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLStyleSP& style, S32 start, S32 end ) : - mStyle( style ), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end, BOOL is_visible) : - mStyle(new LLStyle(is_visible,color,LLStringUtil::null)), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) : - mStyle(new LLStyle(TRUE, color,LLStringUtil::null )), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) : - mStyle(new LLStyle(TRUE, color,LLStringUtil::null )), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} - -BOOL LLTextSegment::getToolTip(std::string& msg) const -{ - if (mToken && !mToken->getToolTip().empty()) - { - const LLWString& wmsg = mToken->getToolTip(); - msg = wstring_to_utf8str(wmsg); - return TRUE; - } - return FALSE; -} - - - -void LLTextSegment::dump() const -{ - llinfos << "Segment [" << -// mColor.mV[VX] << ", " << -// mColor.mV[VY] << ", " << -// mColor.mV[VZ] << "]\t[" << - mStart << ", " << - getEnd() << "]" << - llendl; - -} - -/////////////////////////////////////////////////////////////////// -// Refactoring note: We may eventually want to replace this with boost::regex or -// boost::tokenizer capabilities since we've already fixed at least two JIRAs -// concerning logic issues associated with this function. -S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const -{ - std::string openers=" \t\n('\"[{<>"; - std::string closers=" \t\n)'\"]}><;"; - - if (reverse) - { - for (int index=pos; index >= 0; index--) - { - char c = line[index]; - S32 m2 = openers.find(c); - if (m2 >= 0) - { - return index+1; - } - } - return 0; // index is -1, don't want to return that. - } - else - { - // adjust the search slightly, to allow matching parenthesis inside the URL - S32 paren_count = 0; - for (int index=pos; index<(S32)line.length(); index++) - { - char c = line[index]; - - if (c == '(') - { - paren_count++; - } - else if (c == ')') - { - if (paren_count <= 0) - { - return index; - } - else - { - paren_count--; - } - } - else - { - S32 m2 = closers.find(c); - if (m2 >= 0) - { - return index; - } - } - } - return line.length(); - } -} - -BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const -{ - - S32 m1,m2,m3; - BOOL matched = FALSE; - - m1=line.find("://",*end); - - if (m1 >= 0) //Easy match. - { - *begin = findHTMLToken(line, m1, TRUE); - *end = findHTMLToken(line, m1, FALSE); - - //Load_url only handles http and https so don't hilite ftp, smb, etc. - m2 = line.substr(*begin,(m1 - *begin)).find("http"); - m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); - - std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; - - if (m2 >= 0 || m3>=0) - { - S32 bn = badneighbors.find(line.substr(m1+3,1)); - - if (bn < 0) - { - matched = TRUE; - } - } - } -/* matches things like secondlife.com (no http://) needs a whitelist to really be effective. - else //Harder match. - { - m1 = line.find(".",*end); - - if (m1 >= 0) - { - *end = findHTMLToken(line, m1, FALSE); - *begin = findHTMLToken(line, m1, TRUE); - - m1 = line.rfind(".",*end); - - if ( ( *end - m1 ) > 2 && m1 > *begin) - { - std::string badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`"; - m2 = badneighbors.find(line.substr(m1+1,1)); - m3 = badneighbors.find(line.substr(m1-1,1)); - if (m3<0 && m2<0) - { - matched = TRUE; - } - } - } - } - */ - - if (matched) - { - S32 strpos, strpos2; - - std::string url = line.substr(*begin,*end - *begin); - std::string slurlID = "slurl.com/secondlife/"; - strpos = url.find(slurlID); - - if (strpos < 0) - { - slurlID="secondlife://"; - strpos = url.find(slurlID); - } - - if (strpos < 0) - { - slurlID="sl://"; - strpos = url.find(slurlID); - } - - if (strpos >= 0) - { - strpos+=slurlID.length(); - - while ( ( strpos2=url.find("/",strpos) ) == -1 ) - { - if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " ) - { - matched=FALSE; - break; - } - - strpos = (*end + 1) - *begin; - - *end = findHTMLToken(line,(*begin + strpos),FALSE); - url = line.substr(*begin,*end - *begin); - } - } - - } - - if (!matched) - { - *begin=*end=0; - } - return matched; -} - - - void LLTextEditor::updateAllowingLanguageInput() { LLWindow* window = getWindow(); @@ -4450,7 +2576,7 @@ void LLTextEditor::resetPreedit() deselect(); } - mCursorPos = mPreeditPositions.front(); + setCursorPos(mPreeditPositions.front()); removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); @@ -4490,7 +2616,7 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) { - mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length()); + mPreeditOverwrittenWString = getWText().substr(insert_preedit_at, mPreeditWString.length()); removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); } else @@ -4501,11 +2627,12 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, mPreeditStandouts = preedit_standouts; - needsReflow(); setCursorPos(insert_preedit_at + caret_position); // Update of the preedit should be caused by some key strokes. - mKeystrokeTimer.reset(); + resetCursorBlink(); + + onKeyStroke(); } BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const @@ -4513,7 +2640,7 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect if (control) { LLRect control_rect_screen; - localRectToScreen(mTextRect, &control_rect_screen); + localRectToScreen(mVisibleTextRect, &control_rect_screen); LLUI::screenRectToGL(control_rect_screen, control); } @@ -4534,7 +2661,7 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect return FALSE; } - const S32 first_visible_line = mScrollbar->getDocPos(); + const S32 first_visible_line = getFirstVisibleLine(); if (query < getLineStart(first_visible_line)) { return FALSE; @@ -4560,12 +2687,12 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect const LLWString textString(getWText()); const llwchar * const text = textString.c_str(); - const S32 line_height = llround(mGLFont->getLineHeight()); + const S32 line_height = llround(mDefaultFont->getLineHeight()); if (coord) { - const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems); - const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; + const S32 query_x = mVisibleTextRect.mLeft + mDefaultFont->getWidth(text, current_line_start, query - current_line_start); + const S32 query_y = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; S32 query_screen_x, query_screen_y; localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); @@ -4573,23 +2700,23 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect if (bounds) { - S32 preedit_left = mTextRect.mLeft; + S32 preedit_left = mVisibleTextRect.mLeft; if (preedit_left_position > current_line_start) { - preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems); + preedit_left += mDefaultFont->getWidth(text, current_line_start, preedit_left_position - current_line_start); } - S32 preedit_right = mTextRect.mLeft; + S32 preedit_right = mVisibleTextRect.mLeft; if (preedit_right_position < current_line_end) { - preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, current_line_start, preedit_right_position - current_line_start); } else { - preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, current_line_start, current_line_end - current_line_start); } - const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height; + const S32 preedit_top = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height; const S32 preedit_bottom = preedit_top - line_height; const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); @@ -4663,10 +2790,39 @@ void LLTextEditor::markAsPreedit(S32 position, S32 length) S32 LLTextEditor::getPreeditFontSize() const { - return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); + return llround(mDefaultFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); } -LLWString LLTextEditor::getWText() const +BOOL LLTextEditor::isDirty() const +{ + if(mReadOnly) + { + return FALSE; + } + + if( mPristineCmd ) + { + return ( mPristineCmd == mLastCmd ); + } + else + { + return ( NULL != mLastCmd ); + } +} + +void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& callback) +{ + mKeystrokeSignal.connect(callback); +} + +void LLTextEditor::onKeyStroke() +{ + mKeystrokeSignal(this); +} + +//virtual +void LLTextEditor::clear() { - return getViewModel()->getDisplay(); + getViewModel()->setDisplay(LLWStringUtil::null); + clearSegments(); } |