text: /**
   * @file llscripteditor.cpp
   * @author Cinder Roxley
   * @brief Text editor widget used for viewing and editing scripts
   *
   * $LicenseInfo:firstyear=2001&license=viewerlgpl$
   * Second Life Viewer Source Code
   * Copyright (C) 2012, 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.
   *
   * 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.
   *
   * 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
   *
   * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
   * $/LicenseInfo$
   */
  
  #include "llviewerprecompiledheaders.h"
  #include "llscripteditor.h"
  
  #include "llsyntaxid.h"
  #include "lllocalcliprect.h"
  
  // <FS:Ansariel> FIRE-23047: Increase width of line number column
  //const S32	UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;
  const S32	UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 40;
  
  static LLDefaultChildRegistry::Register<LLScriptEditor> r("script_editor");
  
  LLScriptEditor::Params::Params()
  :	show_line_numbers("show_line_numbers", true)
  {}
  
  
  LLScriptEditor::LLScriptEditor(const Params& p)
  :	LLTextEditor(p)
  ,	mShowLineNumbers(p.show_line_numbers)
  {
  	if (mShowLineNumbers)
  	{
  		mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
  		updateRects();
  	}
  }
  
  void LLScriptEditor::draw()
  {
  	{
  		// 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);
  	}
  	
  	LLTextBase::draw();
  	drawLineNumbers();
  	
      drawPreeditMarker();
  	
  	//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);
  }
  
  void LLScriptEditor::drawLineNumbers()
  {
  	LLGLSUIDefault gls_ui;
  	LLRect scrolled_view_rect = getVisibleDocumentRect();
  	LLRect content_rect = getVisibleTextRect();
  	LLLocalClipRect clip(content_rect);
  	S32 first_line = getFirstVisibleLine();
  	S32 num_lines = getLineCount();
  	if (first_line >= num_lines)
  	{
  		return;
  	}
  	
  	S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum;
  	
  	if (mShowLineNumbers)
  	{
  		//S32 left = 0; // <FS:Ansariel> FIRE-6955: Line numbers not using correct transparency
  		S32 top = getRect().getHeight();
  		S32 bottom = 0;
  
  		// <FS:Ansariel> FIRE-6955: Line numbers not using correct transparency
  		//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
  		gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3 % getCurrentTransparency()); // separator
  		// </FS:Ansariel>
  		
  		S32 last_line_num = -1;
  		
  		for (S32 cur_line = first_line; cur_line < num_lines; cur_line++)
  		{
  			line_info& line = mLineInfoList[cur_line];
  			
  			if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom)
  			{
  				break;
  			}
  			
  			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)
  			{
  				// <FS:Ansariel> Script editor ignoring font selection
  				//const LLFontGL *num_font = LLFontGL::getFontMonospace();
  				const LLFontGL *num_font = getFont();
  				// </FS:Ansariel>
  				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;
  			}
  		}
  	}
  }
  
  void LLScriptEditor::initKeywords()
  {
  	mKeywords.initialize(LLSyntaxIdLSL::getInstance()->getKeywordsXML());
  }
  
  LLTrace::BlockTimerStatHandle FTM_SYNTAX_HIGHLIGHTING("Syntax Highlighting");
  
  void LLScriptEditor::loadKeywords()
  {
  	LL_RECORD_BLOCK_TIME(FTM_SYNTAX_HIGHLIGHTING);
  	mKeywords.processTokens();
  	
  	segment_vec_t segment_list;
  	mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this);
  	
  	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);
  	}
  }
  
  // <FS:Ansariel> Re-add legacy format support
  void LLScriptEditor::loadKeywords(const std::string& filename,
  								const std::vector<std::string>& funcs,
  								const std::vector<std::string>& tooltips,
  								const LLColor3& color)
  {
  	LL_RECORD_BLOCK_TIME(FTM_SYNTAX_HIGHLIGHTING);
  	if (mKeywords.loadFromLegacyFile(filename))
  	{
  		S32 count = llmin(funcs.size(), tooltips.size());
  		for(S32 i = 0; i < count; i++)
  		{
  			std::string name = utf8str_trim(funcs[i]);
  			mKeywords.addToken(LLKeywordToken::TT_WORD, name, color, tooltips[i] );
  		}
  		segment_vec_t segment_list;
  		mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this);
  
  		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);
  		}
  	}
  }
  // </FS:Ansariel>
  
  void LLScriptEditor::updateSegments()
  {
  	if (mReflowIndex < S32_MAX && mKeywords.isLoaded() && mParseOnTheFly)
  	{
  		LL_RECORD_BLOCK_TIME(FTM_SYNTAX_HIGHLIGHTING);
  		// HACK:  No non-ascii keywords for now
  		segment_vec_t segment_list;
  		mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this);
  		
  		clearSegments();
  		for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it)
  		{
  			insertSegment(*list_it);
  		}
  	}
  	
  	LLTextBase::updateSegments();
  }
  
  void LLScriptEditor::clearSegments()
  {
  	if (!mSegments.empty())
  	{
  		mSegments.clear();
  	}
  }
  
  // Most of this is shamelessly copied from LLTextBase
  void LLScriptEditor::drawSelectionBackground()
  {
  	// Draw selection even if we don't have keyboard focus for search/replace
  	if( hasSelection() && !mLineInfoList.empty())
  	{
  		std::vector<LLRect> selection_rects;
  		
  		S32 selection_left		= llmin( mSelectionStart, mSelectionEnd );
  		S32 selection_right		= llmax( mSelectionStart, mSelectionEnd );
  		
  		// Skip through the lines we aren't drawing.
  		LLRect content_display_rect = getVisibleDocumentRect();
  		
  		// binary search for line that starts before top of visible buffer
  		line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, LLTextBase::compare_bottom());
  		line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, LLTextBase::compare_top());
  		
  		bool done = false;
  		
  		// Find the coordinates of the selected area
  		for (;line_iter != end_iter && !done; ++line_iter)
  		{
  			// is selection visible on this line?
  			if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right)
  			{
  				segment_set_t::iterator segment_iter;
  				S32 segment_offset;
  				getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
  				
  				LLRect selection_rect;
  				selection_rect.mLeft = line_iter->mRect.mLeft;
  				selection_rect.mRight = line_iter->mRect.mLeft;
  				selection_rect.mBottom = line_iter->mRect.mBottom;
  				selection_rect.mTop = line_iter->mRect.mTop;
  				
  				for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0)
  				{
  					LLTextSegmentPtr segmentp = *segment_iter;
  					
  					S32 segment_line_start = segmentp->getStart() + segment_offset;
  					S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
  					
  					if (segment_line_start > segment_line_end) break;
  					
  					S32 segment_width = 0;
  					S32 segment_height = 0;
  					
  					// if selection after beginning of segment
  					if(selection_left >= segment_line_start)
  					{
  						S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start;
  						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  						selection_rect.mLeft += segment_width;
  					}
  					
  					// if selection_right == segment_line_end then that means we are the first character of the next segment
  					// or first character of the next line, in either case we want to add the length of the current segment
  					// to the selection rectangle and continue.
  					// if selection right > segment_line_end then selection spans end of current segment...
  					if (selection_right >= segment_line_end)
  					{
  						// extend selection slightly beyond end of line
  						// to indicate selection of newline character (use "n" character to determine width)
  						S32 num_chars = segment_line_end - segment_line_start;
  						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  						selection_rect.mRight += segment_width;
  					}
  					// else if selection ends on current segment...
  					else
  					{
  						S32 num_chars = selection_right - segment_line_start;
  						segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height);
  						selection_rect.mRight += segment_width;
  						
  						break;
  					}
  				}
  				selection_rects.push_back(selection_rect);
  			}
  		}
  		
  		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  		const LLColor4& color = mReadOnly ? mReadOnlyFgColor : mFgColor;
  		F32 alpha = hasFocus() ? 0.7f : 0.3f;
  		alpha *= getDrawContext().mAlpha;
  		// We want to shift the color to something readable but distinct
  		LLColor4 selection_color((1.f + color.mV[VRED]) * 0.5f,
  								 (1.f + color.mV[VGREEN]) * 0.5f,
  								 (1.f + color.mV[VBLUE]) * 0.5f,
  								 alpha);
  		
  		for (std::vector<LLRect>::iterator rect_it = selection_rects.begin();
  			 rect_it != selection_rects.end();
  			 ++rect_it)
  		{
  			LLRect selection_rect = *rect_it;
  			selection_rect = *rect_it;
  			selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
  			gl_rect_2d(selection_rect, selection_color);
  		}
  	}
  }
  
  // <FS> Improved Home-key behavior
  //virtual
  void LLScriptEditor::startOfLine()
  {
  	const LLWString& text = getWText();
  	const S32 line_start_pos = mCursorPos - getLineOffsetFromDocIndex(mCursorPos);
  
  	S32 line_end_pos;
  	S32 line = getLineNumFromDocIndex(mCursorPos);
  	S32 num_lines = getLineCount();
  	if (line + 1 >= num_lines)
  	{
  		line_end_pos = getLength();
  	}
  	else
  	{
  		line_end_pos = getLineStart(line + 1) - 1;
  	}
  
  	S32 trimmed_line_start_pos = line_start_pos;
  	while (trimmed_line_start_pos < line_end_pos && LLStringOps::isSpace(text[trimmed_line_start_pos]))
  	{
  		++trimmed_line_start_pos;
  	}
  
  	if (mCursorPos != trimmed_line_start_pos || mCursorPos == line_start_pos)
  	{
  		setCursorPos(trimmed_line_start_pos);
  	}
  	else if (mCursorPos == trimmed_line_start_pos)
  	{
  		setCursorPos(line_start_pos);
  	}
  }
  // </FS>
  