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