1 /***********************************************************************
2     created:    Thu Jul 7 2005
3     author:     Paul D Turner <paul@cegui.org.uk>
4 *************************************************************************/
5 /***************************************************************************
6  *   Copyright (C) 2004 - 2006 Paul D Turner & The CEGUI Development Team
7  *
8  *   Permission is hereby granted, free of charge, to any person obtaining
9  *   a copy of this software and associated documentation files (the
10  *   "Software"), to deal in the Software without restriction, including
11  *   without limitation the rights to use, copy, modify, merge, publish,
12  *   distribute, sublicense, and/or sell copies of the Software, and to
13  *   permit persons to whom the Software is furnished to do so, subject to
14  *   the following conditions:
15  *
16  *   The above copyright notice and this permission notice shall be
17  *   included in all copies or substantial portions of the Software.
18  *
19  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21  *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22  *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23  *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
24  *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25  *   OTHER DEALINGS IN THE SOFTWARE.
26  ***************************************************************************/
27 #include "CEGUI/WindowRendererSets/Core/MultiLineEditbox.h"
28 #include "CEGUI/falagard/WidgetLookManager.h"
29 #include "CEGUI/falagard/WidgetLookFeel.h"
30 #include "CEGUI/WindowManager.h"
31 #include "CEGUI/widgets/Scrollbar.h"
32 #include "CEGUI/PropertyHelper.h"
33 #include "CEGUI/Image.h"
34 #include "CEGUI/TplWindowRendererProperty.h"
35 
36 // Start of CEGUI namespace section
37 namespace CEGUI
38 {
39 
40 const String FalagardMultiLineEditbox::TypeName("Core/MultiLineEditbox");
41 
42 const String FalagardMultiLineEditbox::UnselectedTextColourPropertyName( "NormalTextColour" );
43 const String FalagardMultiLineEditbox::SelectedTextColourPropertyName( "SelectedTextColour" );
44 const String FalagardMultiLineEditbox::ActiveSelectionColourPropertyName( "ActiveSelectionColour" );
45 const String FalagardMultiLineEditbox::InactiveSelectionColourPropertyName( "InactiveSelectionColour" );
46 const float FalagardMultiLineEditbox::DefaultCaretBlinkTimeout(0.66f);
47 
FalagardMultiLineEditbox(const String & type)48 FalagardMultiLineEditbox::FalagardMultiLineEditbox(const String& type) :
49     MultiLineEditboxWindowRenderer(type),
50     d_blinkCaret(false),
51     d_caretBlinkTimeout(DefaultCaretBlinkTimeout),
52     d_caretBlinkElapsed(0.0f),
53     d_showCaret(true)
54 {
55 
56     CEGUI_DEFINE_WINDOW_RENDERER_PROPERTY(FalagardMultiLineEditbox,bool,
57         "BlinkCaret", "Property to get/set whether the Editbox caret should blink.  "
58         "Value is either \"true\" or \"false\".",
59         &FalagardMultiLineEditbox::setCaretBlinkEnabled,&FalagardMultiLineEditbox::isCaretBlinkEnabled,
60         false);
61     CEGUI_DEFINE_WINDOW_RENDERER_PROPERTY(FalagardMultiLineEditbox,float,
62         "BlinkCaretTimeout", "Property to get/set the caret blink timeout / speed.  "
63         "Value is a float value indicating the timeout in seconds.",
64         &FalagardMultiLineEditbox::setCaretBlinkTimeout,&FalagardMultiLineEditbox::getCaretBlinkTimeout,
65         0.66f);
66 }
67 
getTextRenderArea(void) const68 Rectf FalagardMultiLineEditbox::getTextRenderArea(void) const
69 {
70     MultiLineEditbox* w = (MultiLineEditbox*)d_window;
71     const WidgetLookFeel& wlf = getLookNFeel();
72     bool v_visible = w->getVertScrollbar()->isVisible();
73     bool h_visible = w->getHorzScrollbar()->isVisible();
74 
75     // if either of the scrollbars are visible, we might want to use another text rendering area
76     if (v_visible || h_visible)
77     {
78         String area_name("TextArea");
79 
80         if (h_visible)
81         {
82             area_name += "H";
83         }
84         if (v_visible)
85         {
86             area_name += "V";
87         }
88         area_name += "Scroll";
89 
90         if (wlf.isNamedAreaDefined(area_name))
91         {
92             return wlf.getNamedArea(area_name).getArea().getPixelRect(*w);
93         }
94     }
95 
96     // default to plain TextArea
97     return wlf.getNamedArea("TextArea").getArea().getPixelRect(*w);
98 }
99 
cacheEditboxBaseImagery()100 void FalagardMultiLineEditbox::cacheEditboxBaseImagery()
101 {
102     MultiLineEditbox* w = (MultiLineEditbox*)d_window;
103     const StateImagery* imagery;
104 
105     // get WidgetLookFeel for the assigned look.
106     const WidgetLookFeel& wlf = getLookNFeel();
107     // try and get imagery for our current state
108     imagery = &wlf.getStateImagery(w->isEffectiveDisabled() ? "Disabled" : (w->isReadOnly() ? "ReadOnly" : "Enabled"));
109     // peform the rendering operation.
110     imagery->render(*w);
111 }
112 
cacheCaretImagery(const Rectf & textArea)113 void FalagardMultiLineEditbox::cacheCaretImagery(const Rectf& textArea)
114 {
115     MultiLineEditbox* w = (MultiLineEditbox*)d_window;
116     const Font* fnt = w->getFont();
117 
118     // require a font so that we can calculate caret position.
119     if (fnt)
120     {
121         // get line that caret is in
122         size_t caretLine = w->getLineNumberFromIndex(w->getCaretIndex());
123 
124         const MultiLineEditbox::LineList& d_lines = w->getFormattedLines();
125 
126         // if caret line is valid.
127         if (caretLine < d_lines.size())
128         {
129             // calculate pixel offsets to where caret should be drawn
130             size_t caretLineIdx = w->getCaretIndex() - d_lines[caretLine].d_startIdx;
131             float ypos = caretLine * fnt->getLineSpacing();
132             float xpos = fnt->getTextAdvance(w->getText().substr(d_lines[caretLine].d_startIdx, caretLineIdx));
133 
134 //             // get base offset to target layer for cursor.
135 //             Renderer* renderer = System::getSingleton().getRenderer();
136 //             float baseZ = renderer->getZLayer(7) - renderer->getCurrentZ();
137 
138             // get WidgetLookFeel for the assigned look.
139             const WidgetLookFeel& wlf = getLookNFeel();
140             // get caret imagery
141             const ImagerySection& caretImagery = wlf.getImagerySection("Caret");
142 
143             // calculate finat destination area for caret
144             Rectf caretArea;
145             caretArea.left(textArea.left() + xpos);
146             caretArea.top(textArea.top() + ypos);
147             caretArea.setWidth(caretImagery.getBoundingRect(*w).getSize().d_width);
148             caretArea.setHeight(fnt->getLineSpacing());
149             caretArea.offset(Vector2f(-w->getHorzScrollbar()->getScrollPosition(), -w->getVertScrollbar()->getScrollPosition()));
150 
151             // cache the caret image for rendering.
152             caretImagery.render(*w, caretArea, 0, &textArea);
153         }
154     }
155 }
156 
render()157 void FalagardMultiLineEditbox::render()
158 {
159     MultiLineEditbox* w = (MultiLineEditbox*)d_window;
160     // render general frame and stuff before we handle the text itself
161     cacheEditboxBaseImagery();
162 
163     // Render edit box text
164     Rectf textarea(getTextRenderArea());
165     cacheTextLines(textarea);
166 
167     // draw caret
168     if ((w->hasInputFocus() && !w->isReadOnly()) &&
169         (!d_blinkCaret || d_showCaret))
170             cacheCaretImagery(textarea);
171 }
172 
cacheTextLines(const Rectf & dest_area)173 void FalagardMultiLineEditbox::cacheTextLines(const Rectf& dest_area)
174 {
175     MultiLineEditbox* w = (MultiLineEditbox*)d_window;
176     // text is already formatted, we just grab the lines and render them with the required alignment.
177     Rectf drawArea(dest_area);
178     float vertScrollPos = w->getVertScrollbar()->getScrollPosition();
179     drawArea.offset(Vector2f(-w->getHorzScrollbar()->getScrollPosition(), -vertScrollPos));
180 
181     const Font* fnt = w->getFont();
182 
183     if (fnt)
184     {
185         // calculate final colours to use.
186         ColourRect colours;
187         const float alpha = w->getEffectiveAlpha();
188         ColourRect normalTextCol;
189         setColourRectToUnselectedTextColour(normalTextCol);
190         normalTextCol.modulateAlpha(alpha);
191         ColourRect selectTextCol;
192         setColourRectToSelectedTextColour(selectTextCol);
193         selectTextCol.modulateAlpha(alpha);
194         ColourRect selectBrushCol;
195         w->hasInputFocus() ? setColourRectToActiveSelectionColour(selectBrushCol) :
196                              setColourRectToInactiveSelectionColour(selectBrushCol);
197         selectBrushCol.modulateAlpha(alpha);
198 
199         const MultiLineEditbox::LineList& d_lines = w->getFormattedLines();
200         const size_t numLines = d_lines.size();
201 
202         // calculate the range of visible lines
203         size_t sidx,eidx;
204         sidx = static_cast<size_t>(vertScrollPos / fnt->getLineSpacing());
205         eidx = 1 + sidx + static_cast<size_t>(dest_area.getHeight() / fnt->getLineSpacing());
206         eidx = ceguimin(eidx, numLines);
207         drawArea.d_min.d_y += fnt->getLineSpacing()*static_cast<float>(sidx);
208 
209         // for each formatted line.
210         for (size_t i = sidx; i < eidx; ++i)
211         {
212             Rectf lineRect(drawArea);
213             const MultiLineEditbox::LineInfo& currLine = d_lines[i];
214             String lineText(w->getTextVisual().substr(currLine.d_startIdx, currLine.d_length));
215 
216             // offset the font little down so that it's centered within its own spacing
217             const float old_top = lineRect.top();
218             lineRect.d_min.d_y += (fnt->getLineSpacing() - fnt->getFontHeight()) * 0.5f;
219 
220             // if it is a simple 'no selection area' case
221             if ((currLine.d_startIdx >= w->getSelectionEndIndex()) ||
222                 ((currLine.d_startIdx + currLine.d_length) <= w->getSelectionStartIndex()) ||
223                 (w->getSelectionBrushImage() == 0))
224             {
225                 colours = normalTextCol;
226                 // render the complete line.
227                 fnt->drawText(w->getGeometryBuffer(), lineText,
228                                 lineRect.getPosition(), &dest_area, colours);
229             }
230             // we have at least some selection highlighting to do
231             else
232             {
233                 // Start of actual rendering section.
234                 String sect;
235                 size_t sectIdx = 0, sectLen;
236                 float selStartOffset = 0.0f, selAreaWidth = 0.0f;
237 
238                 // render any text prior to selected region of line.
239                 if (currLine.d_startIdx < w->getSelectionStartIndex())
240                 {
241                     // calculate length of text section
242                     sectLen = w->getSelectionStartIndex() - currLine.d_startIdx;
243 
244                     // get text for this section
245                     sect = lineText.substr(sectIdx, sectLen);
246                     sectIdx += sectLen;
247 
248                     // get the pixel offset to the beginning of the selection area highlight.
249                     selStartOffset = fnt->getTextAdvance(sect);
250 
251                     // draw this portion of the text
252                     colours = normalTextCol;
253                     fnt->drawText(w->getGeometryBuffer(), sect,
254                                     lineRect.getPosition(), &dest_area, colours);
255 
256                     // set position ready for next portion of text
257                     lineRect.d_min.d_x += selStartOffset;
258                 }
259 
260                 // calculate the length of the selected section
261                 sectLen = ceguimin(w->getSelectionEndIndex() - currLine.d_startIdx, currLine.d_length) - sectIdx;
262 
263                 // get the text for this section
264                 sect = lineText.substr(sectIdx, sectLen);
265                 sectIdx += sectLen;
266 
267                 // get the extent to use as the width of the selection area highlight
268                 selAreaWidth = fnt->getTextAdvance(sect);
269 
270                 const float text_top = lineRect.top();
271                 lineRect.top(old_top);
272 
273                 // calculate area for the selection brush on this line
274                 lineRect.left(drawArea.left() + selStartOffset);
275                 lineRect.right(lineRect.left() + selAreaWidth);
276                 lineRect.bottom(lineRect.top() + fnt->getLineSpacing());
277 
278                 // render the selection area brush for this line
279                 colours = selectBrushCol;
280                 w->getSelectionBrushImage()->render(w->getGeometryBuffer(), lineRect, &dest_area, colours);
281 
282                 // draw the text for this section
283                 colours = selectTextCol;
284                 fnt->drawText(w->getGeometryBuffer(), sect,
285                                 lineRect.getPosition(), &dest_area, colours);
286 
287                 lineRect.top(text_top);
288 
289                 // render any text beyond selected region of line
290                 if (sectIdx < currLine.d_length)
291                 {
292                     // update render position to the end of the selected area.
293                     lineRect.d_min.d_x += selAreaWidth;
294 
295                     // calculate length of this section
296                     sectLen = currLine.d_length - sectIdx;
297 
298                     // get the text for this section
299                     sect = lineText.substr(sectIdx, sectLen);
300 
301                     // render the text for this section.
302                     colours = normalTextCol;
303                     fnt->drawText(w->getGeometryBuffer(), sect,
304                                     lineRect.getPosition(), &dest_area, colours);
305                 }
306             }
307 
308             // update master position for next line in paragraph.
309             drawArea.d_min.d_y += fnt->getLineSpacing();
310         }
311     }
312 }
313 
314 //----------------------------------------------------------------------------//
setColourRectToUnselectedTextColour(ColourRect & colour_rect) const315 void FalagardMultiLineEditbox::setColourRectToUnselectedTextColour(
316                                                 ColourRect& colour_rect) const
317 {
318     setColourRectToOptionalPropertyColour(UnselectedTextColourPropertyName,
319                                           colour_rect);
320 }
321 
322 //----------------------------------------------------------------------------//
setColourRectToSelectedTextColour(ColourRect & colour_rect) const323 void FalagardMultiLineEditbox::setColourRectToSelectedTextColour(
324                                                 ColourRect& colour_rect) const
325 {
326     setColourRectToOptionalPropertyColour(SelectedTextColourPropertyName,
327                                           colour_rect);
328 }
329 
330 //----------------------------------------------------------------------------//
setColourRectToActiveSelectionColour(ColourRect & colour_rect) const331 void FalagardMultiLineEditbox::setColourRectToActiveSelectionColour(
332                                                 ColourRect& colour_rect) const
333 {
334     setColourRectToOptionalPropertyColour(ActiveSelectionColourPropertyName,
335                                           colour_rect);
336 }
337 
338 //----------------------------------------------------------------------------//
setColourRectToInactiveSelectionColour(ColourRect & colour_rect) const339 void FalagardMultiLineEditbox::setColourRectToInactiveSelectionColour(
340                                                 ColourRect& colour_rect) const
341 {
342     setColourRectToOptionalPropertyColour(InactiveSelectionColourPropertyName,
343                                           colour_rect);
344 }
345 
346 //----------------------------------------------------------------------------//
setColourRectToOptionalPropertyColour(const String & propertyName,ColourRect & colour_rect) const347 void FalagardMultiLineEditbox::setColourRectToOptionalPropertyColour(
348                                                 const String& propertyName,
349                                                 ColourRect& colour_rect) const
350 {
351     if (d_window->isPropertyPresent(propertyName))
352         colour_rect =
353             d_window->getProperty<ColourRect>(propertyName);
354     else
355         colour_rect.setColours(0);
356 }
357 
358 //----------------------------------------------------------------------------//
update(float elapsed)359 void FalagardMultiLineEditbox::update(float elapsed)
360 {
361     // do base class stuff
362     WindowRenderer::update(elapsed);
363 
364     // only do the update if we absolutely have to
365     if (d_blinkCaret &&
366         !static_cast<MultiLineEditbox*>(d_window)->isReadOnly() &&
367         static_cast<MultiLineEditbox*>(d_window)->hasInputFocus())
368     {
369         d_caretBlinkElapsed += elapsed;
370 
371         if (d_caretBlinkElapsed > d_caretBlinkTimeout)
372         {
373             d_caretBlinkElapsed = 0.0f;
374             d_showCaret ^= true;
375             // state changed, so need a redraw
376             d_window->invalidate();
377         }
378     }
379 }
380 
381 //----------------------------------------------------------------------------//
isCaretBlinkEnabled() const382 bool FalagardMultiLineEditbox::isCaretBlinkEnabled() const
383 {
384     return d_blinkCaret;
385 }
386 
387 //----------------------------------------------------------------------------//
getCaretBlinkTimeout() const388 float FalagardMultiLineEditbox::getCaretBlinkTimeout() const
389 {
390     return d_caretBlinkTimeout;
391 }
392 
393 //----------------------------------------------------------------------------//
setCaretBlinkEnabled(bool enable)394 void FalagardMultiLineEditbox::setCaretBlinkEnabled(bool enable)
395 {
396     d_blinkCaret = enable;
397 }
398 
399 //----------------------------------------------------------------------------//
setCaretBlinkTimeout(float seconds)400 void FalagardMultiLineEditbox::setCaretBlinkTimeout(float seconds)
401 {
402     d_caretBlinkTimeout = seconds;
403 }
404 
405 //----------------------------------------------------------------------------//
handleFontRenderSizeChange(const Font * const font)406 bool FalagardMultiLineEditbox::handleFontRenderSizeChange(const Font* const font)
407 {
408     const bool res = WindowRenderer::handleFontRenderSizeChange(font);
409 
410     if (d_window->getFont() == font)
411     {
412         d_window->invalidate();
413         static_cast<MultiLineEditbox*>(d_window)->formatText(true);
414         return true;
415     }
416 
417     return res;
418 }
419 
420 } // End of  CEGUI namespace section
421