PageRenderTime 152ms CodeModel.GetById 17ms app.highlight 119ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llui/lltexteditor.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 2866 lines | 2229 code | 428 blank | 209 comment | 373 complexity | d8694d653f94433c71ffb5f50ad37a5c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/** 
   2 * @file lltexteditor.cpp
   3 *
   4 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
   5 * Second Life Viewer Source Code
   6 * Copyright (C) 2010, Linden Research, Inc.
   7 * 
   8 * This library is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU Lesser General Public
  10 * License as published by the Free Software Foundation;
  11 * version 2.1 of the License only.
  12 * 
  13 * This library is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16 * Lesser General Public License for more details.
  17 * 
  18 * You should have received a copy of the GNU Lesser General Public
  19 * License along with this library; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  21 * 
  22 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  23 * $/LicenseInfo$
  24 */
  25
  26// Text editor widget to let users enter a a multi-line ASCII document.
  27
  28#include "linden_common.h"
  29
  30#define LLTEXTEDITOR_CPP
  31#include "lltexteditor.h"
  32
  33#include "llfontfreetype.h" // for LLFontFreetype::FIRST_CHAR
  34#include "llfontgl.h"
  35#include "llgl.h"			// LLGLSUIDefault()
  36#include "lllocalcliprect.h"
  37#include "llrender.h"
  38#include "llui.h"
  39#include "lluictrlfactory.h"
  40#include "llrect.h"
  41#include "llfocusmgr.h"
  42#include "lltimer.h"
  43#include "llmath.h"
  44
  45#include "llclipboard.h"
  46#include "llscrollbar.h"
  47#include "llstl.h"
  48#include "llstring.h"
  49#include "llkeyboard.h"
  50#include "llkeywords.h"
  51#include "llundo.h"
  52#include "llviewborder.h"
  53#include "llcontrol.h"
  54#include "llwindow.h"
  55#include "lltextparser.h"
  56#include "llscrollcontainer.h"
  57#include "llpanel.h"
  58#include "llurlregistry.h"
  59#include "lltooltip.h"
  60#include "llmenugl.h"
  61
  62#include <queue>
  63#include "llcombobox.h"
  64
  65// 
  66// Globals
  67//
  68static LLDefaultChildRegistry::Register<LLTextEditor> r("simple_text_editor");
  69
  70// Compiler optimization, generate extern template
  71template class LLTextEditor* LLView::getChild<class LLTextEditor>(
  72	const std::string& name, BOOL recurse) const;
  73
  74//
  75// Constants
  76//
  77const S32	UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;
  78const S32	UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4;
  79const S32	SPACES_PER_TAB = 4;
  80
  81///////////////////////////////////////////////////////////////////
  82
  83class LLTextEditor::TextCmdInsert : public LLTextBase::TextCmd
  84{
  85public:
  86	TextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws, LLTextSegmentPtr segment)
  87		: TextCmd(pos, group_with_next, segment), mWString(ws)
  88	{
  89	}
  90	virtual ~TextCmdInsert() {}
  91	virtual BOOL execute( LLTextBase* editor, S32* delta )
  92	{
  93		*delta = insert(editor, getPosition(), mWString );
  94		LLWStringUtil::truncate(mWString, *delta);
  95		//mWString = wstring_truncate(mWString, *delta);
  96		return (*delta != 0);
  97	}	
  98	virtual S32 undo( LLTextBase* editor )
  99	{
 100		remove(editor, getPosition(), mWString.length() );
 101		return getPosition();
 102	}
 103	virtual S32 redo( LLTextBase* editor )
 104	{
 105		insert(editor, getPosition(), mWString );
 106		return getPosition() + mWString.length();
 107	}
 108
 109private:
 110	LLWString mWString;
 111};
 112
 113///////////////////////////////////////////////////////////////////
 114class LLTextEditor::TextCmdAddChar : public LLTextBase::TextCmd
 115{
 116public:
 117	TextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc, LLTextSegmentPtr segment)
 118		: TextCmd(pos, group_with_next, segment), mWString(1, wc), mBlockExtensions(FALSE)
 119	{
 120	}
 121	virtual void blockExtensions()
 122	{
 123		mBlockExtensions = TRUE;
 124	}
 125	virtual BOOL canExtend(S32 pos) const
 126	{
 127		// cannot extend text with custom segments
 128		if (!mSegments.empty()) return FALSE;
 129
 130		return !mBlockExtensions && (pos == getPosition() + (S32)mWString.length());
 131	}
 132	virtual BOOL execute( LLTextBase* editor, S32* delta )
 133	{
 134		*delta = insert(editor, getPosition(), mWString);
 135		LLWStringUtil::truncate(mWString, *delta);
 136		//mWString = wstring_truncate(mWString, *delta);
 137		return (*delta != 0);
 138	}
 139	virtual BOOL extendAndExecute( LLTextBase* editor, S32 pos, llwchar wc, S32* delta )	
 140	{ 
 141		LLWString ws;
 142		ws += wc;
 143		
 144		*delta = insert(editor, pos, ws);
 145		if( *delta > 0 )
 146		{
 147			mWString += wc;
 148		}
 149		return (*delta != 0);
 150	}
 151	virtual S32 undo( LLTextBase* editor )
 152	{
 153		remove(editor, getPosition(), mWString.length() );
 154		return getPosition();
 155	}
 156	virtual S32 redo( LLTextBase* editor )
 157	{
 158		insert(editor, getPosition(), mWString );
 159		return getPosition() + mWString.length();
 160	}
 161
 162private:
 163	LLWString	mWString;
 164	BOOL		mBlockExtensions;
 165
 166};
 167
 168///////////////////////////////////////////////////////////////////
 169
 170class LLTextEditor::TextCmdOverwriteChar : public LLTextBase::TextCmd
 171{
 172public:
 173	TextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc)
 174		: TextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {}
 175
 176	virtual BOOL execute( LLTextBase* editor, S32* delta )
 177	{ 
 178		mOldChar = editor->getWText()[getPosition()];
 179		overwrite(editor, getPosition(), mChar);
 180		*delta = 0;
 181		return TRUE;
 182	}	
 183	virtual S32 undo( LLTextBase* editor )
 184	{
 185		overwrite(editor, getPosition(), mOldChar);
 186		return getPosition();
 187	}
 188	virtual S32 redo( LLTextBase* editor )
 189	{
 190		overwrite(editor, getPosition(), mChar);
 191		return getPosition()+1;
 192	}
 193
 194private:
 195	llwchar		mChar;
 196	llwchar		mOldChar;
 197};
 198
 199///////////////////////////////////////////////////////////////////
 200
 201class LLTextEditor::TextCmdRemove : public LLTextBase::TextCmd
 202{
 203public:
 204	TextCmdRemove( S32 pos, BOOL group_with_next, S32 len, segment_vec_t& segments ) :
 205		TextCmd(pos, group_with_next), mLen(len)
 206	{
 207		std::swap(mSegments, segments);
 208	}
 209	virtual BOOL execute( LLTextBase* editor, S32* delta )
 210	{ 
 211		mWString = editor->getWText().substr(getPosition(), mLen);
 212		*delta = remove(editor, getPosition(), mLen );
 213		return (*delta != 0);
 214	}
 215	virtual S32 undo( LLTextBase* editor )
 216	{
 217		insert(editor, getPosition(), mWString);
 218		return getPosition() + mWString.length();
 219	}
 220	virtual S32 redo( LLTextBase* editor )
 221	{
 222		remove(editor, getPosition(), mLen );
 223		return getPosition();
 224	}
 225private:
 226	LLWString	mWString;
 227	S32				mLen;
 228};
 229
 230
 231///////////////////////////////////////////////////////////////////
 232LLTextEditor::Params::Params()
 233:	default_text("default_text"),
 234	prevalidate_callback("prevalidate_callback"),
 235	embedded_items("embedded_items", false),
 236	ignore_tab("ignore_tab", true),
 237	show_line_numbers("show_line_numbers", false),
 238	default_color("default_color"),
 239    commit_on_focus_lost("commit_on_focus_lost", false),
 240	show_context_menu("show_context_menu")
 241{
 242	addSynonym(prevalidate_callback, "text_type");
 243}
 244
 245LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
 246	LLTextBase(p),
 247	mBaseDocIsPristine(TRUE),
 248	mPristineCmd( NULL ),
 249	mLastCmd( NULL ),
 250	mDefaultColor(		p.default_color() ),
 251	mShowLineNumbers ( p.show_line_numbers ),
 252	mCommitOnFocusLost( p.commit_on_focus_lost),
 253	mAllowEmbeddedItems( p.embedded_items ),
 254	mMouseDownX(0),
 255	mMouseDownY(0),
 256	mTabsToNextField(p.ignore_tab),
 257	mPrevalidateFunc(p.prevalidate_callback()),
 258	mContextMenu(NULL),
 259	mShowContextMenu(p.show_context_menu)
 260{
 261	mSourceID.generate();
 262
 263	//FIXME: use image?
 264	LLViewBorder::Params params;
 265	params.name = "text ed border";
 266	params.rect = getLocalRect();
 267	params.bevel_style = LLViewBorder::BEVEL_IN;
 268	params.border_thickness = 1;
 269	params.visible = p.border_visible;
 270	mBorder = LLUICtrlFactory::create<LLViewBorder> (params);
 271	addChild( mBorder );
 272
 273	setText(p.default_text());
 274
 275	if (mShowLineNumbers)
 276	{
 277		mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
 278		updateRects();
 279	}
 280	
 281	mParseOnTheFly = TRUE;
 282}
 283
 284void LLTextEditor::initFromParams( const LLTextEditor::Params& p)
 285{
 286	LLTextBase::initFromParams(p);
 287
 288	// HACK:  text editors always need to be enabled so that we can scroll
 289	LLView::setEnabled(true);
 290
 291	if (p.commit_on_focus_lost.isProvided())
 292	{
 293		mCommitOnFocusLost = p.commit_on_focus_lost;
 294	}
 295	
 296	updateAllowingLanguageInput();
 297}
 298
 299LLTextEditor::~LLTextEditor()
 300{
 301	gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid
 302
 303	// Scrollbar is deleted by LLView
 304	std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
 305
 306	// context menu is owned by menu holder, not us
 307	//delete mContextMenu;
 308}
 309
 310////////////////////////////////////////////////////////////
 311// LLTextEditor
 312// Public methods
 313
 314void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params)
 315{
 316	// validate incoming text if necessary
 317	if (mPrevalidateFunc)
 318	{
 319		LLWString test_text = utf8str_to_wstring(utf8str);
 320		if (!mPrevalidateFunc(test_text))
 321		{
 322			// not valid text, nothing to do
 323			return;
 324		}
 325	}
 326
 327	blockUndo();
 328	deselect();
 329	
 330	mParseOnTheFly = FALSE;
 331	LLTextBase::setText(utf8str, input_params);
 332	mParseOnTheFly = TRUE;
 333
 334	resetDirty();
 335}
 336
 337void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap)
 338{
 339	if (search_text_in.empty())
 340	{
 341		return;
 342	}
 343
 344	LLWString text = getWText();
 345	LLWString search_text = utf8str_to_wstring(search_text_in);
 346	if (case_insensitive)
 347	{
 348		LLWStringUtil::toLower(text);
 349		LLWStringUtil::toLower(search_text);
 350	}
 351	
 352	if (mIsSelecting)
 353	{
 354		LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
 355		
 356		if (selected_text == search_text)
 357		{
 358			// We already have this word selected, we are searching for the next.
 359			setCursorPos(mCursorPos + search_text.size());
 360		}
 361	}
 362	
 363	S32 loc = text.find(search_text,mCursorPos);
 364	
 365	// If Maybe we wrapped, search again
 366	if (wrap && (-1 == loc))
 367	{	
 368		loc = text.find(search_text);
 369	}
 370	
 371	// If still -1, then search_text just isn't found.
 372    if (-1 == loc)
 373	{
 374		mIsSelecting = FALSE;
 375		mSelectionEnd = 0;
 376		mSelectionStart = 0;
 377		return;
 378	}
 379
 380	setCursorPos(loc);
 381	
 382	mIsSelecting = TRUE;
 383	mSelectionEnd = mCursorPos;
 384	mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size()));
 385}
 386
 387BOOL LLTextEditor::replaceText(const std::string& search_text_in, const std::string& replace_text,
 388							   BOOL case_insensitive, BOOL wrap)
 389{
 390	BOOL replaced = FALSE;
 391
 392	if (search_text_in.empty())
 393	{
 394		return replaced;
 395	}
 396
 397	LLWString search_text = utf8str_to_wstring(search_text_in);
 398	if (mIsSelecting)
 399	{
 400		LLWString text = getWText();
 401		LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd);
 402
 403		if (case_insensitive)
 404		{
 405			LLWStringUtil::toLower(selected_text);
 406			LLWStringUtil::toLower(search_text);
 407		}
 408
 409		if (selected_text == search_text)
 410		{
 411			insertText(replace_text);
 412			replaced = TRUE;
 413		}
 414	}
 415
 416	selectNext(search_text_in, case_insensitive, wrap);
 417	return replaced;
 418}
 419
 420void LLTextEditor::replaceTextAll(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive)
 421{
 422	startOfDoc();
 423	selectNext(search_text, case_insensitive, FALSE);
 424
 425	BOOL replaced = TRUE;
 426	while ( replaced )
 427	{
 428		replaced = replaceText(search_text,replace_text, case_insensitive, FALSE);
 429	}
 430}
 431
 432S32 LLTextEditor::prevWordPos(S32 cursorPos) const
 433{
 434	LLWString wtext(getWText());
 435	while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') )
 436	{
 437		cursorPos--;
 438	}
 439	while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) )
 440	{
 441		cursorPos--;
 442	}
 443	return cursorPos;
 444}
 445
 446S32 LLTextEditor::nextWordPos(S32 cursorPos) const
 447{
 448	LLWString wtext(getWText());
 449	while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) )
 450	{
 451		cursorPos++;
 452	} 
 453	while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') )
 454	{
 455		cursorPos++;
 456	}
 457	return cursorPos;
 458}
 459
 460const LLTextSegmentPtr	LLTextEditor::getPreviousSegment() const
 461{
 462	static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment;
 463
 464	index_segment->setStart(mCursorPos);
 465	index_segment->setEnd(mCursorPos);
 466
 467	// find segment index at character to left of cursor (or rightmost edge of selection)
 468	segment_set_t::const_iterator it = mSegments.lower_bound(index_segment);
 469
 470	if (it != mSegments.end())
 471	{
 472		return *it;
 473	}
 474	else
 475	{
 476		return LLTextSegmentPtr();
 477	}
 478}
 479
 480void LLTextEditor::getSelectedSegments(LLTextEditor::segment_vec_t& segments) const
 481{
 482	S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos;
 483	S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos;
 484
 485	return getSegmentsInRange(segments, left, right, true);
 486}
 487
 488void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, S32 start, S32 end, bool include_partial) const
 489{
 490	segment_set_t::const_iterator first_it = getSegIterContaining(start);
 491	segment_set_t::const_iterator end_it = getSegIterContaining(end - 1);
 492	if (end_it != mSegments.end()) ++end_it;
 493
 494	for (segment_set_t::const_iterator it = first_it; it != end_it; ++it)
 495	{
 496		LLTextSegmentPtr segment = *it;
 497		if (include_partial
 498			||	(segment->getStart() >= start
 499				&& segment->getEnd() <= end))
 500		{
 501			segments_out.push_back(segment);
 502		}
 503	}
 504}
 505
 506BOOL LLTextEditor::selectionContainsLineBreaks()
 507{
 508	if (hasSelection())
 509	{
 510		S32 left = llmin(mSelectionStart, mSelectionEnd);
 511		S32 right = left + llabs(mSelectionStart - mSelectionEnd);
 512
 513		LLWString wtext = getWText();
 514		for( S32 i = left; i < right; i++ )
 515		{
 516			if (wtext[i] == '\n')
 517			{
 518				return TRUE;
 519			}
 520		}
 521	}
 522	return FALSE;
 523}
 524
 525
 526S32 LLTextEditor::indentLine( S32 pos, S32 spaces )
 527{
 528	// Assumes that pos is at the start of the line
 529	// spaces may be positive (indent) or negative (unindent).
 530	// Returns the actual number of characters added or removed.
 531
 532	llassert(pos >= 0);
 533	llassert(pos <= getLength() );
 534
 535	S32 delta_spaces = 0;
 536
 537	if (spaces >= 0)
 538	{
 539		// Indent
 540		for(S32 i=0; i < spaces; i++)
 541		{
 542			delta_spaces += addChar(pos, ' ');
 543		}
 544	}
 545	else
 546	{
 547		// Unindent
 548		for(S32 i=0; i < -spaces; i++)
 549		{
 550			LLWString wtext = getWText();
 551			if (wtext[pos] == ' ')
 552			{
 553				delta_spaces += remove( pos, 1, FALSE );
 554			}
 555 		}
 556	}
 557
 558	return delta_spaces;
 559}
 560
 561void LLTextEditor::indentSelectedLines( S32 spaces )
 562{
 563	if( hasSelection() )
 564	{
 565		LLWString text = getWText();
 566		S32 left = llmin( mSelectionStart, mSelectionEnd );
 567		S32 right = left + llabs( mSelectionStart - mSelectionEnd );
 568		BOOL cursor_on_right = (mSelectionEnd > mSelectionStart);
 569		S32 cur = left;
 570
 571		// Expand left to start of line
 572		while( (cur > 0) && (text[cur] != '\n') )
 573		{
 574			cur--;
 575		}
 576		left = cur;
 577		if( cur > 0 )
 578		{
 579			left++;
 580		}
 581
 582		// Expand right to end of line
 583		if( text[right - 1] == '\n' )
 584		{
 585			right--;
 586		}
 587		else
 588		{
 589			while( (text[right] != '\n') && (right <= getLength() ) )
 590			{
 591				right++;
 592			}
 593		}
 594
 595		// Disabling parsing on the fly to avoid updating text segments
 596		// until all indentation commands are executed.
 597		mParseOnTheFly = FALSE;
 598
 599		// Find each start-of-line and indent it
 600		do
 601		{
 602			if( text[cur] == '\n' )
 603			{
 604				cur++;
 605			}
 606
 607			S32 delta_spaces = indentLine( cur, spaces );
 608			if( delta_spaces > 0 )
 609			{
 610				cur += delta_spaces;
 611			}
 612			right += delta_spaces;
 613
 614			text = getWText();
 615
 616			// Find the next new line
 617			while( (cur < right) && (text[cur] != '\n') )
 618			{
 619				cur++;
 620			}
 621		}
 622		while( cur < right );
 623
 624		mParseOnTheFly = TRUE;
 625
 626		if( (right < getLength()) && (text[right] == '\n') )
 627		{
 628			right++;
 629		}
 630
 631		// Set the selection and cursor
 632		if( cursor_on_right )
 633		{
 634			mSelectionStart = left;
 635			mSelectionEnd = right;
 636		}
 637		else
 638		{
 639			mSelectionStart = right;
 640			mSelectionEnd = left;
 641		}
 642		setCursorPos(mSelectionEnd);
 643	}
 644}
 645
 646//virtual
 647BOOL LLTextEditor::canSelectAll() const
 648{
 649	return TRUE;
 650}
 651
 652// virtual
 653void LLTextEditor::selectAll()
 654{
 655	mSelectionStart = getLength();
 656	mSelectionEnd = 0;
 657	setCursorPos(mSelectionEnd);
 658	updatePrimary();
 659}
 660
 661BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
 662{
 663	BOOL	handled = FALSE;
 664
 665	// set focus first, in case click callbacks want to change it
 666	// RN: do we really need to have a tab stop?
 667	if (hasTabStop())
 668	{
 669		setFocus( TRUE );
 670	}
 671
 672	// Let scrollbar have first dibs
 673	handled = LLTextBase::handleMouseDown(x, y, mask);
 674
 675	if( !handled )
 676	{
 677		if (!(mask & MASK_SHIFT))
 678		{
 679			deselect();
 680		}
 681
 682		BOOL start_select = TRUE;
 683		if( start_select )
 684		{
 685			// If we're not scrolling (handled by child), then we're selecting
 686			if (mask & MASK_SHIFT)
 687			{
 688				S32 old_cursor_pos = mCursorPos;
 689				setCursorAtLocalPos( x, y, true );
 690
 691				if (hasSelection())
 692				{
 693					mSelectionEnd = mCursorPos;
 694				}
 695				else
 696				{
 697					mSelectionStart = old_cursor_pos;
 698					mSelectionEnd = mCursorPos;
 699				}
 700				// assume we're starting a drag select
 701				mIsSelecting = TRUE;
 702			}
 703			else
 704			{
 705				setCursorAtLocalPos( x, y, true );
 706				startSelection();
 707			}
 708			gFocusMgr.setMouseCapture( this );
 709		}
 710
 711		handled = TRUE;
 712	}
 713
 714	// Delay cursor flashing
 715	resetCursorBlink();
 716
 717	return handled;
 718}
 719
 720BOOL LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
 721{
 722	if (hasTabStop())
 723	{
 724		setFocus(TRUE);
 725	}
 726	// Prefer editor menu if it has selection. See EXT-6806.
 727	if (hasSelection() || !LLTextBase::handleRightMouseDown(x, y, mask))
 728	{
 729		if(getShowContextMenu())
 730		{
 731			showContextMenu(x, y);
 732		}
 733	}
 734	return TRUE;
 735}
 736
 737
 738
 739BOOL LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
 740{
 741	if (hasTabStop())
 742	{
 743		setFocus(TRUE);
 744	}
 745
 746	if (!LLTextBase::handleMouseDown(x, y, mask))
 747	{
 748		if( canPastePrimary() )
 749		{
 750			setCursorAtLocalPos( x, y, true );
 751			// does not rely on focus being set
 752			pastePrimary();
 753		}
 754	}
 755	return TRUE;
 756}
 757
 758
 759BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
 760{
 761	BOOL handled = FALSE;
 762
 763	if(hasMouseCapture() )
 764	{
 765		if( mIsSelecting ) 
 766		{
 767			if(mScroller)
 768			{	
 769				mScroller->autoScroll(x, y);
 770			}
 771			S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight);
 772			S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop);
 773			setCursorAtLocalPos( clamped_x, clamped_y, true );
 774			mSelectionEnd = mCursorPos;
 775		}
 776		lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;		
 777		getWindow()->setCursor(UI_CURSOR_IBEAM);
 778		handled = TRUE;
 779	}
 780
 781	if( !handled )
 782	{
 783		// Pass to children
 784		handled = LLTextBase::handleHover(x, y, mask);
 785	}
 786
 787	if( handled )
 788	{
 789		// Delay cursor flashing
 790		resetCursorBlink();
 791	}
 792
 793	if( !handled )
 794	{
 795		getWindow()->setCursor(UI_CURSOR_IBEAM);
 796		handled = TRUE;
 797	}
 798
 799	return handled;
 800}
 801
 802
 803BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
 804{
 805	BOOL	handled = FALSE;
 806
 807	// if I'm not currently selecting text
 808	if (!(hasSelection() && hasMouseCapture()))
 809	{
 810		// let text segments handle mouse event
 811		handled = LLTextBase::handleMouseUp(x, y, mask);
 812	}
 813
 814	if( !handled )
 815	{
 816		if( mIsSelecting )
 817		{
 818			if(mScroller)
 819			{
 820				mScroller->autoScroll(x, y);
 821			}
 822			S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight);
 823			S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop);
 824			setCursorAtLocalPos( clamped_x, clamped_y, true );
 825			endSelection();
 826		}
 827		
 828		// take selection to 'primary' clipboard
 829		updatePrimary();
 830
 831		handled = TRUE;
 832	}
 833
 834	// Delay cursor flashing
 835	resetCursorBlink();
 836
 837	if( hasMouseCapture()  )
 838	{
 839		gFocusMgr.setMouseCapture( NULL );
 840		
 841		handled = TRUE;
 842	}
 843
 844	return handled;
 845}
 846
 847
 848BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
 849{
 850	BOOL	handled = FALSE;
 851
 852	// let scrollbar and text segments have first dibs
 853	handled = LLTextBase::handleDoubleClick(x, y, mask);
 854
 855	if( !handled )
 856	{
 857		setCursorAtLocalPos( x, y, false );
 858		deselect();
 859
 860		LLWString text = getWText();
 861		
 862		if( LLWStringUtil::isPartOfWord( text[mCursorPos] ) )
 863		{
 864			// Select word the cursor is over
 865			while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord(text[mCursorPos-1]))
 866			{
 867				if (!setCursorPos(mCursorPos - 1)) break;
 868			}
 869			startSelection();
 870
 871			while ((mCursorPos < (S32)text.length()) && LLWStringUtil::isPartOfWord( text[mCursorPos] ) )
 872			{
 873				if (!setCursorPos(mCursorPos + 1)) break;
 874			}
 875		
 876			mSelectionEnd = mCursorPos;
 877		}
 878		else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) )
 879		{
 880			// Select the character the cursor is over
 881			startSelection();
 882			setCursorPos(mCursorPos + 1);
 883			mSelectionEnd = mCursorPos;
 884		}
 885
 886		// We don't want handleMouseUp() to "finish" the selection (and thereby
 887		// set mSelectionEnd to where the mouse is), so we finish the selection here.
 888		mIsSelecting = FALSE;  
 889
 890		// delay cursor flashing
 891		resetCursorBlink();
 892
 893		// take selection to 'primary' clipboard
 894		updatePrimary();
 895
 896		handled = TRUE;
 897	}
 898
 899	return handled;
 900}
 901
 902
 903//----------------------------------------------------------------------------
 904// Returns change in number of characters in mText
 905
 906S32 LLTextEditor::execute( TextCmd* cmd )
 907{
 908	S32 delta = 0;
 909	if( cmd->execute(this, &delta) )
 910	{
 911		// Delete top of undo stack
 912		undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
 913		std::for_each(mUndoStack.begin(), enditer, DeletePointer());
 914		mUndoStack.erase(mUndoStack.begin(), enditer);
 915		// Push the new command is now on the top (front) of the undo stack.
 916		mUndoStack.push_front(cmd);
 917		mLastCmd = cmd;
 918
 919		bool need_to_rollback = mPrevalidateFunc 
 920								&& !mPrevalidateFunc(getViewModel()->getDisplay());
 921		if (need_to_rollback)
 922		{
 923			// get rid of this last command and clean up undo stack
 924			undo();
 925
 926			// remove any evidence of this command from redo history
 927			mUndoStack.pop_front();
 928			delete cmd;
 929
 930			// failure, nothing changed
 931			delta = 0;
 932		}
 933	}
 934	else
 935	{
 936		// Operation failed, so don't put it on the undo stack.
 937		delete cmd;
 938	}
 939
 940	return delta;
 941}
 942
 943S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment)
 944{
 945	return execute( new TextCmdInsert( pos, group_with_next_op, wstr, segment ) );
 946}
 947
 948S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op)
 949{
 950	S32 end_pos = getEditableIndex(pos + length, true);
 951
 952	segment_vec_t segments_to_remove;
 953	// store text segments
 954	getSegmentsInRange(segments_to_remove, pos, pos + length, false);
 955
 956	return execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) );
 957}
 958
 959S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
 960{
 961	if ((S32)getLength() == pos)
 962	{
 963		return addChar(pos, wc);
 964	}
 965	else
 966	{
 967		return execute(new TextCmdOverwriteChar(pos, FALSE, wc));
 968	}
 969}
 970
 971// Remove a single character from the text.  Tries to remove
 972// a pseudo-tab (up to for spaces in a row)
 973void LLTextEditor::removeCharOrTab()
 974{
 975	if( !getEnabled() )
 976	{
 977		return;
 978	}
 979	if( mCursorPos > 0 )
 980	{
 981		S32 chars_to_remove = 1;
 982
 983		LLWString text = getWText();
 984		if (text[mCursorPos - 1] == ' ')
 985		{
 986			// Try to remove a "tab"
 987			S32 offset = getLineOffsetFromDocIndex(mCursorPos);
 988			if (offset > 0)
 989			{
 990				chars_to_remove = offset % SPACES_PER_TAB;
 991				if( chars_to_remove == 0 )
 992				{
 993					chars_to_remove = SPACES_PER_TAB;
 994				}
 995
 996				for( S32 i = 0; i < chars_to_remove; i++ )
 997				{
 998					if (text[ mCursorPos - i - 1] != ' ')
 999					{
1000						// Fewer than a full tab's worth of spaces, so
1001						// just delete a single character.
1002						chars_to_remove = 1;
1003						break;
1004					}
1005				}
1006			}
1007		}
1008	
1009		for (S32 i = 0; i < chars_to_remove; i++)
1010		{
1011			setCursorPos(mCursorPos - 1);
1012			remove( mCursorPos, 1, FALSE );
1013		}
1014	}
1015	else
1016	{
1017		LLUI::reportBadKeystroke();
1018	}
1019}
1020
1021// Remove a single character from the text
1022S32 LLTextEditor::removeChar(S32 pos)
1023{
1024	return remove( pos, 1, FALSE );
1025}
1026
1027void LLTextEditor::removeChar()
1028{
1029	if (!getEnabled())
1030	{
1031		return;
1032	}
1033	if (mCursorPos > 0)
1034	{
1035		setCursorPos(mCursorPos - 1);
1036		removeChar(mCursorPos);
1037	}
1038	else
1039	{
1040		LLUI::reportBadKeystroke();
1041	}
1042}
1043
1044// Add a single character to the text
1045S32 LLTextEditor::addChar(S32 pos, llwchar wc)
1046{
1047	if ( (wstring_utf8_length( getWText() ) + wchar_utf8_length( wc ))  > mMaxTextByteLength)
1048	{
1049		make_ui_sound("UISndBadKeystroke");
1050		return 0;
1051	}
1052
1053	if (mLastCmd && mLastCmd->canExtend(pos))
1054	{
1055		S32 delta = 0;
1056		if (mPrevalidateFunc)
1057		{
1058			// get a copy of current text contents
1059			LLWString test_string(getViewModel()->getDisplay());
1060
1061			// modify text contents as if this addChar succeeded
1062			llassert(pos <= (S32)test_string.size());
1063			test_string.insert(pos, 1, wc);
1064			if (!mPrevalidateFunc( test_string))
1065			{
1066				return 0;
1067			}
1068		}
1069		mLastCmd->extendAndExecute(this, pos, wc, &delta);
1070
1071		return delta;
1072	}
1073	else
1074	{
1075		return execute(new TextCmdAddChar(pos, FALSE, wc, LLTextSegmentPtr()));
1076	}
1077}
1078
1079void LLTextEditor::addChar(llwchar wc)
1080{
1081	if( !getEnabled() )
1082	{
1083		return;
1084	}
1085	if( hasSelection() )
1086	{
1087		deleteSelection(TRUE);
1088	}
1089	else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
1090	{
1091		removeChar(mCursorPos);
1092	}
1093
1094	setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
1095}
1096void LLTextEditor::addLineBreakChar()
1097{
1098	if( !getEnabled() )
1099	{
1100		return;
1101	}
1102	if( hasSelection() )
1103	{
1104		deleteSelection(TRUE);
1105	}
1106	else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
1107	{
1108		removeChar(mCursorPos);
1109	}
1110
1111	LLStyleConstSP sp(new LLStyle(LLStyle::Params()));
1112	LLTextSegmentPtr segment = new LLLineBreakTextSegment(sp, mCursorPos);
1113
1114	S32 pos = execute(new TextCmdAddChar(mCursorPos, FALSE, '\n', segment));
1115	
1116	setCursorPos(mCursorPos + pos);
1117}
1118
1119
1120BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask)
1121{
1122	BOOL handled = FALSE;
1123
1124	if( mask & MASK_SHIFT )
1125	{
1126		handled = TRUE;
1127		
1128		switch( key )
1129		{
1130		case KEY_LEFT:
1131			if( 0 < mCursorPos )
1132			{
1133				startSelection();
1134				setCursorPos(mCursorPos - 1);
1135				if( mask & MASK_CONTROL )
1136				{
1137					setCursorPos(prevWordPos(mCursorPos));
1138				}
1139				mSelectionEnd = mCursorPos;
1140			}
1141			break;
1142
1143		case KEY_RIGHT:
1144			if( mCursorPos < getLength() )
1145			{
1146				startSelection();
1147				setCursorPos(mCursorPos + 1);
1148				if( mask & MASK_CONTROL )
1149				{
1150					setCursorPos(nextWordPos(mCursorPos));
1151				}
1152				mSelectionEnd = mCursorPos;
1153			}
1154			break;
1155
1156		case KEY_UP:
1157			startSelection();
1158			changeLine( -1 );
1159			mSelectionEnd = mCursorPos;
1160			break;
1161
1162		case KEY_PAGE_UP:
1163			startSelection();
1164			changePage( -1 );
1165			mSelectionEnd = mCursorPos;
1166			break;
1167
1168		case KEY_HOME:
1169			startSelection();
1170			if( mask & MASK_CONTROL )
1171			{
1172				setCursorPos(0);
1173			}
1174			else
1175			{
1176				startOfLine();
1177			}
1178			mSelectionEnd = mCursorPos;
1179			break;
1180
1181		case KEY_DOWN:
1182			startSelection();
1183			changeLine( 1 );
1184			mSelectionEnd = mCursorPos;
1185			break;
1186
1187		case KEY_PAGE_DOWN:
1188			startSelection();
1189			changePage( 1 );
1190			mSelectionEnd = mCursorPos;
1191			break;
1192
1193		case KEY_END:
1194			startSelection();
1195			if( mask & MASK_CONTROL )
1196			{
1197				setCursorPos(getLength());
1198			}
1199			else
1200			{
1201				endOfLine();
1202			}
1203			mSelectionEnd = mCursorPos;
1204			break;
1205
1206		default:
1207			handled = FALSE;
1208			break;
1209		}
1210	}
1211
1212	if( handled )
1213	{
1214		// take selection to 'primary' clipboard
1215		updatePrimary();
1216	}
1217 
1218	return handled;
1219}
1220
1221BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask)
1222{
1223	BOOL handled = FALSE;
1224
1225	// Ignore capslock key
1226	if( MASK_NONE == mask )
1227	{
1228		handled = TRUE;
1229		switch( key )
1230		{
1231		case KEY_UP:
1232			changeLine( -1 );
1233			break;
1234
1235		case KEY_PAGE_UP:
1236			changePage( -1 );
1237			break;
1238
1239		case KEY_HOME:
1240			startOfLine();
1241			break;
1242
1243		case KEY_DOWN:
1244			changeLine( 1 );
1245			deselect();
1246			break;
1247
1248		case KEY_PAGE_DOWN:
1249			changePage( 1 );
1250			break;
1251 
1252		case KEY_END:
1253			endOfLine();
1254			break;
1255
1256		case KEY_LEFT:
1257			if( hasSelection() )
1258			{
1259				setCursorPos(llmin( mSelectionStart, mSelectionEnd ));
1260			}
1261			else
1262			{
1263				if( 0 < mCursorPos )
1264				{
1265					setCursorPos(mCursorPos - 1);
1266				}
1267				else
1268				{
1269					LLUI::reportBadKeystroke();
1270				}
1271			}
1272			break;
1273
1274		case KEY_RIGHT:
1275			if( hasSelection() )
1276			{
1277				setCursorPos(llmax( mSelectionStart, mSelectionEnd ));
1278			}
1279			else
1280			{
1281				if( mCursorPos < getLength() )
1282				{
1283					setCursorPos(mCursorPos + 1);
1284				}
1285				else
1286				{
1287					LLUI::reportBadKeystroke();
1288				}
1289			}	
1290			break;
1291			
1292		default:
1293			handled = FALSE;
1294			break;
1295		}
1296	}
1297	
1298	if (handled)
1299	{
1300		deselect();
1301	}
1302	
1303	return handled;
1304}
1305
1306void LLTextEditor::deleteSelection(BOOL group_with_next_op )
1307{
1308	if( getEnabled() && hasSelection() )
1309	{
1310		S32 pos = llmin( mSelectionStart, mSelectionEnd );
1311		S32 length = llabs( mSelectionStart - mSelectionEnd );
1312	
1313		remove( pos, length, group_with_next_op );
1314
1315		deselect();
1316		setCursorPos(pos);
1317	}
1318}
1319
1320// virtual
1321BOOL LLTextEditor::canCut() const
1322{
1323	return !mReadOnly && hasSelection();
1324}
1325
1326// cut selection to clipboard
1327void LLTextEditor::cut()
1328{
1329	if( !canCut() )
1330	{
1331		return;
1332	}
1333	S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
1334	S32 length = llabs( mSelectionStart - mSelectionEnd );
1335	gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID );
1336	deleteSelection( FALSE );
1337
1338	onKeyStroke();
1339}
1340
1341BOOL LLTextEditor::canCopy() const
1342{
1343	return hasSelection();
1344}
1345
1346// copy selection to clipboard
1347void LLTextEditor::copy()
1348{
1349	if( !canCopy() )
1350	{
1351		return;
1352	}
1353	S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
1354	S32 length = llabs( mSelectionStart - mSelectionEnd );
1355	gClipboard.copyFromSubstring(getWText(), left_pos, length, mSourceID);
1356}
1357
1358BOOL LLTextEditor::canPaste() const
1359{
1360	return !mReadOnly && gClipboard.canPasteString();
1361}
1362
1363// paste from clipboard
1364void LLTextEditor::paste()
1365{
1366	bool is_primary = false;
1367	pasteHelper(is_primary);
1368}
1369
1370// paste from primary
1371void LLTextEditor::pastePrimary()
1372{
1373	bool is_primary = true;
1374	pasteHelper(is_primary);
1375}
1376
1377// paste from primary (itsprimary==true) or clipboard (itsprimary==false)
1378void LLTextEditor::pasteHelper(bool is_primary)
1379{
1380	mParseOnTheFly = FALSE;
1381	bool can_paste_it;
1382	if (is_primary)
1383	{
1384		can_paste_it = canPastePrimary();
1385	}
1386	else
1387	{
1388		can_paste_it = canPaste();
1389	}
1390
1391	if (!can_paste_it)
1392	{
1393		return;
1394	}
1395
1396	LLUUID source_id;
1397	LLWString paste;
1398	if (is_primary)
1399	{
1400		paste = gClipboard.getPastePrimaryWString(&source_id);
1401	}
1402	else 
1403	{
1404		paste = gClipboard.getPasteWString(&source_id);
1405	}
1406
1407	if (paste.empty())
1408	{
1409		return;
1410	}
1411
1412	// Delete any selected characters (the paste replaces them)
1413	if( (!is_primary) && hasSelection() )
1414	{
1415		deleteSelection(TRUE);
1416	}
1417
1418	// Clean up string (replace tabs and remove characters that our fonts don't support).
1419	LLWString clean_string(paste);
1420	LLWStringUtil::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB);
1421	if( mAllowEmbeddedItems )
1422	{
1423		const llwchar LF = 10;
1424		S32 len = clean_string.length();
1425		for( S32 i = 0; i < len; i++ )
1426		{
1427			llwchar wc = clean_string[i];
1428			if( (wc < LLFontFreetype::FIRST_CHAR) && (wc != LF) )
1429			{
1430				clean_string[i] = LL_UNKNOWN_CHAR;
1431			}
1432			else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR)
1433			{
1434				clean_string[i] = pasteEmbeddedItem(wc);
1435			}
1436		}
1437	}
1438
1439	// Insert the new text into the existing text.
1440
1441	//paste text with linebreaks.
1442	std::basic_string<llwchar>::size_type start = 0;
1443	std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start);
1444	
1445	while(pos!=-1)
1446	{
1447		if(pos!=start)
1448		{
1449			std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,pos-start);
1450			setCursorPos(mCursorPos + insert(mCursorPos, str, FALSE, LLTextSegmentPtr()));
1451		}
1452		addLineBreakChar();
1453		
1454		start = pos+1;
1455		pos = clean_string.find('\n',start);
1456	}
1457
1458	std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,clean_string.length()-start);
1459	setCursorPos(mCursorPos + insert(mCursorPos, str, FALSE, LLTextSegmentPtr()));
1460
1461	deselect();
1462
1463	onKeyStroke();
1464	mParseOnTheFly = TRUE;
1465}
1466
1467
1468
1469// copy selection to primary
1470void LLTextEditor::copyPrimary()
1471{
1472	if( !canCopy() )
1473	{
1474		return;
1475	}
1476	S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
1477	S32 length = llabs( mSelectionStart - mSelectionEnd );
1478	gClipboard.copyFromPrimarySubstring(getWText(), left_pos, length, mSourceID);
1479}
1480
1481BOOL LLTextEditor::canPastePrimary() const
1482{
1483	return !mReadOnly && gClipboard.canPastePrimaryString();
1484}
1485
1486void LLTextEditor::updatePrimary()
1487{
1488	if (canCopy())
1489	{
1490		copyPrimary();
1491	}
1492}
1493
1494BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask)	
1495{
1496	BOOL handled = FALSE;
1497
1498	if( mask & MASK_CONTROL )
1499	{
1500		handled = TRUE;
1501
1502		switch( key )
1503		{
1504		case KEY_HOME:
1505			if( mask & MASK_SHIFT )
1506			{
1507				startSelection();
1508				setCursorPos(0);
1509				mSelectionEnd = mCursorPos;
1510			}
1511			else
1512			{
1513				// Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1514				// all move the cursor as if clicking, so should deselect.
1515				deselect();
1516				startOfDoc();
1517			}
1518			break;
1519
1520		case KEY_END:
1521			{
1522				if( mask & MASK_SHIFT )
1523				{
1524					startSelection();
1525				}
1526				else
1527				{
1528					// Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1529					// all move the cursor as if clicking, so should deselect.
1530					deselect();
1531				}
1532				endOfDoc();
1533				if( mask & MASK_SHIFT )
1534				{
1535					mSelectionEnd = mCursorPos;
1536				}
1537				break;
1538			}
1539
1540		case KEY_RIGHT:
1541			if( mCursorPos < getLength() )
1542			{
1543				// Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1544				// all move the cursor as if clicking, so should deselect.
1545				deselect();
1546
1547				setCursorPos(nextWordPos(mCursorPos + 1));
1548			}
1549			break;
1550
1551
1552		case KEY_LEFT:
1553			if( mCursorPos > 0 )
1554			{
1555				// Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
1556				// all move the cursor as if clicking, so should deselect.
1557				deselect();
1558
1559				setCursorPos(prevWordPos(mCursorPos - 1));
1560			}
1561			break;
1562
1563		default:
1564			handled = FALSE;
1565			break;
1566		}
1567	}
1568
1569	if (handled)
1570	{
1571		updatePrimary();
1572	}
1573
1574	return handled;
1575}
1576
1577
1578BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask)	
1579	{
1580	BOOL handled = TRUE;
1581
1582	if (mReadOnly) return FALSE;
1583
1584	switch( key )
1585	{
1586	case KEY_INSERT:
1587		if (mask == MASK_NONE)
1588		{
1589			gKeyboard->toggleInsertMode();
1590		}
1591		break;
1592
1593	case KEY_BACKSPACE:
1594		if( hasSelection() )
1595		{
1596			deleteSelection(FALSE);
1597		}
1598		else
1599		if( 0 < mCursorPos )
1600		{
1601			removeCharOrTab();
1602		}
1603		else
1604		{
1605			LLUI::reportBadKeystroke();
1606		}
1607		break;
1608
1609
1610	case KEY_RETURN:
1611		if (mask == MASK_NONE)
1612		{
1613			if( hasSelection() )
1614			{
1615				deleteSelection(FALSE);
1616			}
1617			autoIndent(); // TODO: make this optional
1618		}
1619		else
1620		{
1621			handled = FALSE;
1622			break;
1623		}
1624		break;
1625
1626	case KEY_TAB:
1627		if (mask & MASK_CONTROL)
1628		{
1629			handled = FALSE;
1630			break;
1631		}
1632		if( hasSelection() && selectionContainsLineBreaks() )
1633		{
1634			indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB );
1635		}
1636		else
1637		{
1638			if( hasSelection() )
1639			{
1640				deleteSelection(FALSE);
1641			}
1642			
1643			S32 offset = getLineOffsetFromDocIndex(mCursorPos);
1644
1645			S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
1646			for( S32 i=0; i < spaces_needed; i++ )
1647			{
1648				addChar( ' ' );
1649			}
1650		}
1651		break;
1652		
1653	default:
1654		handled = FALSE;
1655		break;
1656	}
1657
1658	if (handled)
1659	{
1660		onKeyStroke();
1661	}
1662	return handled;
1663}
1664
1665
1666void LLTextEditor::unindentLineBeforeCloseBrace()
1667{
1668	if( mCursorPos >= 1 )
1669	{
1670		LLWString text = getWText();
1671		if( ' ' == text[ mCursorPos - 1 ] )
1672		{
1673			removeCharOrTab();
1674		}
1675	}
1676}
1677
1678
1679BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask )
1680{
1681	BOOL	handled = FALSE;
1682
1683	// Special case for TAB.  If want to move to next field, report
1684	// not handled and let the parent take care of field movement.
1685	if (KEY_TAB == key && mTabsToNextField)
1686	{
1687		return FALSE;
1688	}
1689		
1690	if (mReadOnly && mScroller)
1691	{
1692		handled = (mScroller && mScroller->handleKeyHere( key, mask ))
1693				|| handleSelectionKey(key, mask)
1694				|| handleControlKey(key, mask);
1695		}
1696		else 
1697		{
1698		handled = handleNavigationKey( key, mask )
1699				|| handleSelectionKey(key, mask)
1700				|| handleControlKey(key, mask)
1701				|| handleSpecialKey(key, mask);
1702	}
1703
1704	if( handled )
1705	{
1706		resetCursorBlink();
1707		needsScroll();
1708	}
1709
1710	return handled;
1711}
1712
1713
1714BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char)
1715{
1716	if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
1717	{
1718		return FALSE;
1719	}
1720
1721	BOOL	handled = FALSE;
1722
1723	// Handle most keys only if the text editor is writeable.
1724	if( !mReadOnly )
1725	{
1726		if( '}' == uni_char )
1727		{
1728			unindentLineBeforeCloseBrace();
1729		}
1730
1731		// TODO: KLW Add auto show of tool tip on (
1732		addChar( uni_char );
1733
1734		// Keys that add characters temporarily hide the cursor
1735		getWindow()->hideCursorUntilMouseMove();
1736
1737		handled = TRUE;
1738	}
1739
1740	if( handled )
1741	{
1742		resetCursorBlink();
1743
1744		// Most keystrokes will make the selection box go away, but not all will.
1745		deselect();
1746
1747		onKeyStroke();
1748	}
1749
1750	return handled;
1751}
1752
1753
1754// virtual
1755BOOL LLTextEditor::canDoDelete() const
1756{
1757	return !mReadOnly && ( hasSelection() || (mCursorPos < getLength()) );
1758}
1759
1760void LLTextEditor::doDelete()
1761{
1762	if( !canDoDelete() )
1763	{
1764		return;
1765	}
1766	if( hasSelection() )
1767	{
1768		deleteSelection(FALSE);
1769	}
1770	else
1771	if( mCursorPos < getLength() )
1772	{	
1773		S32 i;
1774		S32 chars_to_remove = 1;
1775		LLWString text = getWText();
1776		if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) )
1777		{
1778			// Try to remove a full tab's worth of spaces
1779			S32 offset = getLineOffsetFromDocIndex(mCursorPos);
1780			chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
1781			if( chars_to_remove == 0 )
1782			{
1783				chars_to_remove = SPACES_PER_TAB;
1784			}
1785
1786			for( i = 0; i < chars_to_remove; i++ )
1787			{
1788				if( text[mCursorPos + i] != ' ' )
1789				{
1790					chars_to_remove = 1;
1791					break;
1792				}
1793			}
1794		}
1795
1796		for( i = 0; i < chars_to_remove; i++ )
1797		{
1798			setCursorPos(mCursorPos + 1);
1799			removeChar();
1800		}
1801
1802	}
1803
1804	onKeyStroke();
1805}
1806
1807//----------------------------------------------------------------------------
1808
1809
1810void LLTextEditor::blockUndo()
1811{
1812	mBaseDocIsPristine = FALSE;
1813	mLastCmd = NULL;
1814	std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
1815	mUndoStack.clear();
1816}
1817
1818// virtual
1819BOOL LLTextEditor::canUndo() const
1820{
1821	return !mReadOnly && mLastCmd != NULL;
1822}
1823
1824void LLTextEditor::undo()
1825{
1826	if( !canUndo() )
1827	{
1828		return;
1829	}
1830	deselect();
1831	S32 pos = 0;
1832	do
1833	{
1834		pos = mLastCmd->undo(this);
1835		undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
1836		if (iter != mUndoStack.end())
1837			++iter;
1838		if (iter != mUndoStack.end())
1839			mLastCmd = *iter;
1840		else
1841			mLastCmd = NULL;
1842
1843		} while( mLastCmd && mLastCmd->groupWithNext() );
1844
1845		setCursorPos(pos);
1846
1847	onKeyStroke();
1848}
1849
1850BOOL LLTextEditor::canRedo() const
1851{
1852	return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front());
1853}
1854
1855void LLTextEditor::redo()
1856{
1857	if( !canRedo() )
1858	{
1859		return;
1860	}
1861	deselect();
1862	S32 pos = 0;
1863	do
1864	{
1865		if( !mLastCmd )
1866		{
1867			mLastCmd = mUndoStack.back();
1868		}
1869		else
1870		{
1871			undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd);
1872			if (iter != mUndoStack.begin())
1873				mLastCmd = *(--iter);
1874			else
1875				mLastCmd = NULL;
1876		}
1877
1878			if( mLastCmd )
1879			{
1880				pos = mLastCmd->redo(this);
1881			}
1882		} while( 
1883			mLastCmd &&
1884			mLastCmd->groupWithNext() &&
1885			(mLastCmd != mUndoStack.front()) );
1886		
1887		setCursorPos(pos);
1888
1889	onKeyStroke();
1890}
1891
1892void LLTextEditor::onFocusReceived()
1893{
1894	LLTextBase::onFocusReceived();
1895	updateAllowingLanguageInput();
1896}
1897
1898// virtual, from LLView
1899void LLTextEditor::onFocusLost()
1900{
1901	updateAllowingLanguageInput();
1902
1903	// Route menu back to the default
1904 	if( gEditMenuHandler == this )
1905	{
1906		gEditMenuHandler = NULL;
1907	}
1908
1909	if (mCommitOnFocusLost)
1910	{
1911		onCommit();
1912	}
1913
1914	// Make sure cursor is shown again
1915	getWindow()->showCursorFromMouseMove();
1916
1917	LLTextBase::onFocusLost();
1918}
1919
1920void LLTextEditor::onCommit()
1921{
1922	setControlValue(getValue()); 
1923	LLTextBase::onCommit(); 
1924}
1925
1926void LLTextEditor::setEnabled(BOOL enabled)
1927{
1928	// just treat enabled as read-only flag
1929	bool read_only = !enabled;
1930	if (read_only != mReadOnly)
1931	{
1932		//mReadOnly = read_only;
1933		LLTextBase::setReadOnly(read_only);
1934		updateSegments();
1935		updateAllowingLanguageInput();
1936	}
1937}
1938
1939void LLTextEditor::showContextMenu(S32 x, S32 y)
1940{
1941	if (!mContextMenu)
1942	{
1943		mContextMenu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>("menu_text_editor.xml", 
1944																				LLMenuGL::sMenuContainer, 
1945																				LLMenuHolderGL::child_registry_t::instance());
1946	}
1947
1948	// Route menu to this class
1949	// previously this was done in ::handleRightMoseDown:
1950	//if(hasTabStop())
1951	// setFocus(TRUE)  - why? weird...
1952	// and then inside setFocus
1953	// ....
1954	//    gEditMenuHandler = this;
1955	// ....
1956	// but this didn't work in all cases and just weird...
1957    //why not here? 
1958	// (all this was done for EXT-4443)
1959
1960	gEditMenuHandler = this;
1961
1962	S32 screen_x, screen_y;
1963	localPointToScreen(x, y, &screen_x, &screen_y);
1964	mContextMenu->show(screen_x, screen_y);
1965}
1966
1967
1968void LLTextEditor::drawPreeditMarker()
1969{
1970	static LLUICachedControl<F32> preedit_marker_brightness ("UIPreeditMarkerBrightness", 0);
1971	static LLUICachedControl<S32> preedit_marker_gap ("UIPreeditMarkerGap", 0);
1972	static LLUICachedControl<S32> preedit_marker_position ("UIPreeditMarkerPosition", 0);
1973	static LLUICachedControl<S32> preedit_marker_thickness ("UIPreeditMarkerThickness", 0);
1974	static LLUICachedControl<F32> preedit_standout_brightness ("UIPreeditStandoutBrightness", 0);
1975	static LLUICachedControl<S32> preedit_standout_gap ("UIPreeditStandoutGap", 0);
1976	static LLUICachedControl<S32> preedit_standout_position ("UIPreeditStandoutPosition", 0);
1977	static LLUICachedControl<S32> preedit_standout_thickness ("UIPreeditStandoutThickness", 0);
1978
1979	if (!hasPreeditString())
1980	{
1981		return;
1982	}
1983
1984    const LLWString textString(getWText());
1985	const llwchar *text = textString.c_str();
1986	const S32 text_len = getLength();
1987	const S32 num_lines = getLineCount();
1988
1989	S32 cur_line = getFirstVisibleLine();
1990	if (cur_line >= num_lines)
1991	{
1992		return;
1993	}
1994		
1995	const S32 line_height = llround( mDefaultFont->getLineHeight() );
1996
1997	S32 line_start = getLineStart(cur_line);
1998	S32 line_y = mVisibleTextRect.mTop - line_height;
1999	while((mVisibleTextRect.mBottom <= line_y) && (num_lines > cur_line))
2000	{
2001		S32 next_start = -1;
2002		S32 line_end = text_len;
2003
2004		if ((cur_line + 1) < num_lines)
2005		{
2006			next_start = getLineStart(cur_line + 1);
2007			line_end = next_start;
2008		}
2009		if ( text[line_end-1] == '\n' )
2010		{
2011			--line_end;
2012		}
2013
2014		// Does this line contain preedits?
2015		if (line_start >= mPreeditPositions.back())
2016		{
2017			// We have passed the preedits.
2018			break;
2019		}
2020		if (line_end > mPreeditPositions.front())
2021		{
2022			for (U32 i = 0; i < mPreeditStandouts.size(); i++)
2023			{
2024				S32 left = mPreeditPositions[i];
2025				S32 right = mPreeditPositions[i + 1];
2026				if (right <= line_start || left >= line_end)
2027				{
2028					continue;
2029				}
2030
2031				S32 preedit_left = mVisibleTextRect.mLeft;
2032				if (left > line_start)
2033				{
2034					preedit_left += mDefaultFont->getWidth(text, line_start, left - line_start);
2035				}
2036				S32 preedit_right = mVisibleTextRect.mLeft;
2037				if (right < line_end)
2038				{
2039					preedit_right += mDefaultFont->getWidth(text, line_start, right - line_start);
2040				}
2041				else
2042				{
2043					preedit_right += mDefaultFont->getWidth(text, line_start, line_end - line_start);
2044				}
2045
2046				if (mPreeditStandouts[i])
2047				{
2048					gl_rect_2d(preedit_left + preedit_standout_gap,
2049							line_y + preedit_standout_position,
2050							preedit_right - preedit_standout_gap - 1,
2051							line_y + preedit_standout_position - preedit_standout_thickness,
2052							(mCursorColor.get() * preedit_standout_brightness + mWriteableBgColor.get() * (1 - preedit_standout_brightness)).setAlpha(1.0f));
2053				}
2054				else
2055				{
2056					gl_rect_2d(preedit_left + preedit_marker_gap,
2057							line_y + preedit_marker_position,
2058							preedit_right - preedit_marker_gap - 1,
2059							line_y + preedit_marker_position - preedit_marker_thickness,
2060							(mCursorColor.get() * preedit_marker_brightness + mWriteableBgColor.get() * (1 - preedit_marker_brightness)).setAlpha(1.0f));
2061				}
2062			}
2063		}
2064
2065		// move down one line
2066		line_y -= line_height;
2067		line_start = next_start;
2068		cur_line++;
2069	}
2070}
2071
2072
2073void LLTextEditor::drawLineNumbers()
2074{
2075	LLGLSUIDefault gls_ui;
2076	LLRect scrolled_view_rect = getVisibleDocumentRect();
2077	LLRect content_rect = getVisibleTextRect();	
2078	LLLocalClipRect clip(content_rect);
2079	S32 first_line = getFirstVisibleLine();
2080	S32 num_lines = getLineCount();
2081	if (first_line >= num_lines)
2082	{
2083		return;
2084	}
2085	
2086	S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum;
2087
2088	if (mShowLineNumbers)
2089	{
2090		S32 left = 0;
2091		S32 top = getRect().getHeight();
2092		S32 bottom = 0;
2093
2094		gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only
2095		gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator
2096
2097		S32 last_line_num = -1;
2098
2099		for (S32 cur_line = first_line; cur_line < num_lines; cur_line++)
2100		{
2101			line_info& line = mLineInfoList[cur_line];
2102
2103			if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom) 
2104			{
2105				break;
2106			}
2107
2108			S32 line_bottom = line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom;
2109			// draw the line numbers
2110			if(line.mLineNum != last_line_num && line.mRect.mTop <= scrolled_view_rect.mTop) 
2111			{
2112				const LLFontGL *num_font = LLFontGL::getFontMonospace();
2113				const LLWString ltext = utf8str_to_wstring(llformat("%d", line.mLineNum ));
2114				BOOL is_cur_line = cursor_line == line.mLineNum;
2115				const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL;
2116				const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor;
2117				num_font->render( 
2118					ltext, // string to draw
2119					0, // begin offset
2120					UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2, // x
2121					line_bottom, // y
2122					fg_color, 
2123					LLFontGL::RIGHT, // horizontal alignment
2124					LLFontGL::BOTTOM, // vertical alignment
2125					style,
2126					LLFontGL::NO_SHADOW,
2127					S32_MAX, // max chars
2128					UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2); // max pixels
2129				last_line_num = line.mLineNum;
2130			}
2131		}
2132	}
2133}
2134
2135void LLTextEditor::draw()
2136{
2137	{
2138		// pad clipping rectangle so that cursor can draw at full width
2139		// when at left edge of mVisibleTextRect
2140		LLRect clip_rect(mVisibleTextRect);
2141		clip_rect.stretch(1);
2142		LLLocalClipRect clip(clip_rect);
2143		drawPreeditMarker();
2144	}
2145
2146	LLTextBase::draw();
2147	drawLineNumbers();
2148
2149	//RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret
2150	// when in readonly mode
2151	mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly);
2152}
2153
2154// Start or stop the editor from accepting text-editing keystrokes
2155// see also LLLineEditor
2156void LLTextEditor::setFocus( BOOL new_state )
2157{
2158	BOOL old_state = hasFocus();
2159
2160	// Don't change anything if the focus state didn't change
2161	if (new_state == old_state) return;
2162
2163	// Notify early if we are losing focus.
2164	if (!new_state)
2165	{
2166		getWindow()->allowLanguageTextInput(this, FALSE);
2167	}
2168
2169	LLTextBase::setFocus( new_state );
2170
2171	if( new_state )
2172	{
2173		// Route menu to this class
2174		gEditMenuHandler = this;
2175
2176		// Don't start the cursor flashing right away
2177		resetCursorBlink();
2178	}
2179	else
2180	{
2181		// Route menu back to the default
2182		if( gEditMenuHandler == this )
2183		{
2184			gEditMenuHandler = NULL;
2185		}
2186
2187		endSelection();
2188	}
2189}
2190
2191// public
2192void LLTextEditor::setCursorAndScrollToEnd()
2193{
2194	deselect();
2195	endOfDoc();
2196}
2197
2198void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ) 
2199{ 
2200	*line = getLineNumFromDocIndex(mCursorPos, include_wordwrap);
2201	*col = getLineOffsetFromDocIndex(mCursorPos, include_wordwrap);
2202}
2203
2204void LLTextEditor::autoIndent()
2205{
2206	// Count the number of spaces in the current line
2207	S32 line = getLineNumFromDocIndex(mCursorPos, false);
2208	S32 line_start = getLineStart(line);
2209	S32 space_count = 0;
2210	S32 i;
2211
2212	LLWString text = getWText();
2213	while( ' ' == text[line_start] )
2214	{
2215		space_count++;
2216		line_start++;
2217	}
2218
2219	// If we're starting a braced section, indent one level.
2220	if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') )
2221	{
2222		space_count += SPACES_PER_TAB;
2223	}
2224
2225	// Insert that number of spaces on the new line
2226
2227	//appendLineBreakSegment(LLStyle::Params());//addChar( '\n' );
2228	addLineBreakChar();
2229
2230	for( i = 0; i < space_count; i++ )
2231	{
2232		addChar( ' ' );
2233	}
2234}
2235
2236// Inserts new text at the cursor position
2237void LLTextEditor::insertText(const std::string &new_text)
2238{
2239	BOOL enabled = getEnabled();
2240	setEnabled( TRUE );
2241
2242	// Delete any selected characters (the insertion replaces them)
2243	if( hasSelection() )
2244	{
2245		deleteSelection(TRUE);
2246	}
2247
2248	setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE, LLTextSegmentPtr() ));
2249	
2250	setEnabled( enabled );
2251}
2252
2253void LLTextEditor::insertText(LLWString &new_text)
2254{
2255	BOOL enabled = getEnabled();
2256	setEnabled( TRUE );
2257
2258	// Delete any selected characters (the insertion replaces them)
2259	if( hasSelection() )
2260	{
2261		deleteSelection(TRUE);
2262	}
2263
2264	setCursorPos(mCursorPos + insert( mCursorPos, new_text, FALSE, LLTextSegmentPtr() ));
2265
2266	setEnabled( enabled );
2267}
2268
2269void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo)
2270{
2271	// Save old state
2272	S32 selection_start = mSelectionStart;
2273	S32 selection_end = mSelectionEnd;
2274	BOOL was_selecting = mIsSelecting;
2275	S32 cursor_pos = mCursorPos;
2276	S32 old_length = getLength();
2277	BOOL cursor_was_at_end = (mCursorPos == old_length);
2278
2279	deselect();
2280
2281	setCursorPos(old_length);
2282
2283	LLWString widget_wide_text = utf8str_to_wstring(text);
2284
2285	LLTextSegmentPtr segment = new LLInlineViewSegment(params, old_length, old_length + widget_wide_text.size());
2286	insert(getLength(), widget_wide_text, FALSE, segment);
2287
2288	// Set the cursor and scroll position
2289	if( selection_start != selection_end )
2290	{
2291		mSelectionStart = selection_start;
2292		mSelectionEnd = selection_end;
2293
2294		mIsSelecting = was_selecting;
2295		setCursorPos(cursor_pos);
2296	}
2297	else if( cursor_was_at_end )
2298	{
2299		setCursorPos(getLength());
2300	}
2301	else
2302	{
2303		setCursorPos(cursor_pos);
2304	}
2305
2306	if (!allow_undo)
2307	{
2308		blockUndo();
2309	}
2310}
2311
2312void LLTextEditor::removeTextFromEnd(S32 num_chars)
2313{
2314	if (num_chars <= 0) return;
2315
2316	remove(getLength() - num_chars, num_chars, FALSE);
2317
2318	S32 len = getLength();
2319	setCursorPos (llclamp(mCursorPos, 0, len));
2320	mSelectionStart = llclamp(mSelectionStart, 0, len);
2321	mSelectionEnd = llclamp(mSelectionEnd, 0, len);
2322
2323	needsScroll();
2324}
2325
2326//----------------------------------------------------------------------------
2327
2328void LLTextEditor::makePristine()
2329{
2330	mPristineCmd = mLastCmd;
2331	mBaseDocIsPristine = !mLastCmd;
2332
2333	// Create a clean partition in the undo stack.  We don't want a single command to extend from
2334	// the "pre-pristine" state to the "post-pristine" state.
2335	if( mLastCmd )
2336	{
2337		mLastCmd->blockExtensions();
2338	}
2339}
2340
2341BOOL LLTextEditor::isPristine() const
2342{
2343	if( mPristineCmd )
2344	{
2345		return (mPristineCmd == mLastCmd);
2346	}
2347	else
2348	{
2349		// No undo stack, so check if the version before and commands were done was the original version
2350		return !mLastCmd && mBaseDocIsPristine;
2351	}
2352}
2353
2354BOOL LLTextEditor::tryToRevertToPristineState()
2355{
2356	if( !isPristine() )
2357	{
2358		deselect();
2359		S32 i = 0;
2360		while( !isPristine() && canUndo() )
2361		{
2362			undo();
2363			i--;
2364		}
2365
2366		while( !isPristine() && canRedo() )
2367		{
2368			redo();
2369			i++;
2370		}
2371
2372		if( !isPristine() )
2373		{
2374			

Large files files are truncated, but you can click here to view the full file