1 /***********************************************************************
2 created: Sat Jun 25 2005
3 author: Paul D Turner <paul@cegui.org.uk>
4 *************************************************************************/
5 /***************************************************************************
6 * Copyright (C) 2004 - 2009 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/Editbox.h"
28 #include "CEGUI/falagard/WidgetLookManager.h"
29 #include "CEGUI/falagard/WidgetLookFeel.h"
30 #include "CEGUI/falagard/XMLEnumHelper.h"
31 #include "CEGUI/PropertyHelper.h"
32 #include "CEGUI/CoordConverter.h"
33 #include "CEGUI/Font.h"
34 #include "CEGUI/BidiVisualMapping.h"
35 #include "CEGUI/TplWindowRendererProperty.h"
36
37 #include <stdio.h>
38
39 // Start of CEGUI namespace section
40 namespace CEGUI
41 {
42 //----------------------------------------------------------------------------//
43 const String FalagardEditbox::TypeName("Core/Editbox");
44
45 const String FalagardEditbox::UnselectedTextColourPropertyName( "NormalTextColour" );
46 const String FalagardEditbox::SelectedTextColourPropertyName( "SelectedTextColour" );
47 const String FalagardEditbox::ActiveSelectionColourPropertyName( "ActiveSelectionColour" );
48 const String FalagardEditbox::InactiveSelectionColourPropertyName( "InactiveSelectionColour" );
49
50 const float FalagardEditbox::DefaultCaretBlinkTimeout(0.66f);
51
52 //----------------------------------------------------------------------------//
FalagardEditbox(const String & type)53 FalagardEditbox::FalagardEditbox(const String& type) :
54 EditboxWindowRenderer(type),
55 d_lastTextOffset(0),
56 d_blinkCaret(false),
57 d_caretBlinkTimeout(DefaultCaretBlinkTimeout),
58 d_caretBlinkElapsed(0.0f),
59 d_showCaret(true),
60 d_textFormatting(HTF_LEFT_ALIGNED)
61 {
62 CEGUI_DEFINE_WINDOW_RENDERER_PROPERTY(FalagardEditbox, bool,
63 "BlinkCaret", "Property to get/set whether the Editbox caret should blink. "
64 "Value is either \"true\" or \"false\".",
65 &FalagardEditbox::setCaretBlinkEnabled, &FalagardEditbox::isCaretBlinkEnabled,
66 false);
67 CEGUI_DEFINE_WINDOW_RENDERER_PROPERTY(FalagardEditbox, float,
68 "BlinkCaretTimeout", "Property to get/set the caret blink timeout / speed. "
69 "Value is a float value indicating the timeout in seconds.",
70 &FalagardEditbox::setCaretBlinkTimeout, &FalagardEditbox::getCaretBlinkTimeout,
71 DefaultCaretBlinkTimeout);
72 CEGUI_DEFINE_WINDOW_RENDERER_PROPERTY(FalagardEditbox, HorizontalTextFormatting,
73 "TextFormatting", "Property to get/set the horizontal formatting mode. "
74 "Value is one of: LeftAligned, RightAligned or HorzCentred",
75 &FalagardEditbox::setTextFormatting, &FalagardEditbox::getTextFormatting,
76 HTF_LEFT_ALIGNED);
77 }
78
79 //----------------------------------------------------------------------------//
render()80 void FalagardEditbox::render()
81 {
82 const WidgetLookFeel& wlf = getLookNFeel();
83
84 renderBaseImagery(wlf);
85
86 // no font == no more rendering
87 const Font* font = d_window->getFont();
88 if (!font)
89 return;
90
91 String visual_text;
92 setupVisualString(visual_text);
93
94 const ImagerySection& caret_imagery = wlf.getImagerySection("Caret");
95
96 // get destination area for text
97 const Rectf text_area(wlf.getNamedArea("TextArea").getArea().getPixelRect(*d_window));
98
99 const size_t caret_index = getCaretIndex(visual_text);
100 const float extent_to_caret = font->getTextAdvance(visual_text.substr(0, caret_index));
101 const float caret_width = caret_imagery.getBoundingRect(*d_window, text_area).getWidth();
102 const float text_extent = font->getTextExtent(visual_text);
103 const float text_offset = calculateTextOffset(text_area, text_extent, caret_width, extent_to_caret);
104
105 #ifdef CEGUI_BIDI_SUPPORT
106 renderTextBidi(wlf, visual_text, text_area, text_offset);
107 #else
108 renderTextNoBidi(wlf, visual_text, text_area, text_offset);
109 #endif
110
111 // remember this for next time.
112 d_lastTextOffset = text_offset;
113
114 renderCaret(caret_imagery, text_area, text_offset, extent_to_caret);
115 }
116
117 //----------------------------------------------------------------------------//
renderBaseImagery(const WidgetLookFeel & wlf) const118 void FalagardEditbox::renderBaseImagery(const WidgetLookFeel& wlf) const
119 {
120 Editbox* w = static_cast<Editbox*>(d_window);
121
122 const StateImagery* imagery = &wlf.getStateImagery(
123 w->isEffectiveDisabled() ? "Disabled" : (w->isReadOnly() ? "ReadOnly" : "Enabled"));
124
125 imagery->render(*w);
126 }
127
128 //----------------------------------------------------------------------------//
setupVisualString(String & visual) const129 void FalagardEditbox::setupVisualString(String& visual) const
130 {
131 Editbox* w = static_cast<Editbox*>(d_window);
132
133 if (w->isTextMasked())
134 visual.assign(w->getText().length(), w->getMaskCodePoint());
135 else
136 visual.assign(w->getTextVisual());
137 }
138
139 //----------------------------------------------------------------------------//
getCaretIndex(const String & visual_text) const140 size_t FalagardEditbox::getCaretIndex(const String& visual_text) const
141 {
142 Editbox* w = static_cast<Editbox*>(d_window);
143
144 size_t caretIndex = w->getCaretIndex();
145
146 #ifdef CEGUI_BIDI_SUPPORT
147 // the char before the caret bidi type
148 bool currCharIsRtl = false;
149 if (!visual_text.empty() && caretIndex > 0)
150 {
151 size_t curCaretIndex = w->getCaretIndex();
152 BidiCharType charBeforeCaretType = w->getBidiVisualMapping()->
153 getBidiCharType(visual_text[curCaretIndex - 1]);
154 // for neutral chars you decide by the char after
155 for (; BCT_NEUTRAL == charBeforeCaretType &&
156 (visual_text.size() > curCaretIndex); curCaretIndex++)
157 {
158 charBeforeCaretType = w->getBidiVisualMapping()->
159 getBidiCharType(visual_text[curCaretIndex - 1]);
160 }
161
162 currCharIsRtl = (BCT_RIGHT_TO_LEFT == charBeforeCaretType);
163 }
164
165 const bool isFirstChar = caretIndex == 0;
166
167 // the pos is by the char before
168 if (!isFirstChar)
169 caretIndex--;
170
171 // we need to find the caret pos by the logical to visual map
172 if (w->getBidiVisualMapping()->getV2lMapping().size() > caretIndex)
173 caretIndex = w->getBidiVisualMapping()->getL2vMapping()[caretIndex];
174
175 // for non RTL char - the caret pos is after the char
176 if (!currCharIsRtl)
177 caretIndex++;
178
179 // if first char is not rtl - we need to stand at the start of the line
180 if (isFirstChar)
181 {
182 bool firstCharRtl =
183 !visual_text.empty() &&
184 (BCT_RIGHT_TO_LEFT == w->getBidiVisualMapping()->
185 getBidiCharType(visual_text[0]));
186
187 if (!firstCharRtl)
188 caretIndex--;
189 }
190 #else
191 CEGUI_UNUSED(visual_text);
192 #endif
193
194 return caretIndex;
195 }
196
197 //----------------------------------------------------------------------------//
calculateTextOffset(const Rectf & text_area,const float text_extent,const float caret_width,const float extent_to_caret)198 float FalagardEditbox::calculateTextOffset(const Rectf& text_area,
199 const float text_extent,
200 const float caret_width,
201 const float extent_to_caret)
202 {
203 // if caret is to the left of the box
204 if ((d_lastTextOffset + extent_to_caret) < 0)
205 return -extent_to_caret;
206
207 // if caret is off to the right.
208 if ((d_lastTextOffset + extent_to_caret) >= (text_area.getWidth() - caret_width))
209 return text_area.getWidth() - extent_to_caret - caret_width;
210
211 // handle formatting of text when it's shorter than the available space
212 if (text_extent < text_area.getWidth())
213 {
214 if (d_textFormatting == HTF_CENTRE_ALIGNED)
215 return (text_area.getWidth() - text_extent) / 2;
216
217 if (d_textFormatting == HTF_RIGHT_ALIGNED)
218 return text_area.getWidth() - text_extent;
219 }
220
221 // no change to text position; re-use last offset value.
222 return d_lastTextOffset;
223 }
224
225 //----------------------------------------------------------------------------//
renderTextNoBidi(const WidgetLookFeel & wlf,const String & text,const Rectf & text_area,float text_offset)226 void FalagardEditbox::renderTextNoBidi(const WidgetLookFeel& wlf,
227 const String& text,
228 const Rectf& text_area,
229 float text_offset)
230 {
231 const Font* font = d_window->getFont();
232
233 // setup initial rect for text formatting
234 Rectf text_part_rect(text_area);
235 // allow for scroll position
236 text_part_rect.d_min.d_x += text_offset;
237 // centre text vertically within the defined text area
238 text_part_rect.d_min.d_y += (text_area.getHeight() - font->getFontHeight()) * 0.5f;
239
240 ColourRect colours;
241 const float alpha_comp = d_window->getEffectiveAlpha();
242 // get unhighlighted text colour (saves accessing property twice)
243 ColourRect unselectedColours;
244 setColourRectToUnselectedTextColour(unselectedColours);
245 // see if the editbox is active or inactive.
246 Editbox* const w = static_cast<Editbox*>(d_window);
247 const bool active = editboxIsFocussed();
248
249 if (w->getSelectionLength() != 0)
250 {
251 // calculate required start and end offsets of selection imagery.
252 float selStartOffset =
253 font->getTextAdvance(text.substr(0, w->getSelectionStartIndex()));
254 float selEndOffset =
255 font->getTextAdvance(text.substr(0, w->getSelectionEndIndex()));
256
257 // calculate area for selection imagery.
258 Rectf hlarea(text_area);
259 hlarea.d_min.d_x += text_offset + selStartOffset;
260 hlarea.d_max.d_x = hlarea.d_min.d_x + (selEndOffset - selStartOffset);
261
262 // render the selection imagery.
263 wlf.getStateImagery(active ? "ActiveSelection" :
264 "InactiveSelection").
265 render(*w, hlarea, 0, &text_area);
266 }
267
268 // draw pre-highlight text
269 String sect = text.substr(0, w->getSelectionStartIndex());
270 colours = unselectedColours;
271 colours.modulateAlpha(alpha_comp);
272 text_part_rect.d_min.d_x =
273 font->drawText(w->getGeometryBuffer(), sect,
274 text_part_rect.getPosition(), &text_area, colours);
275
276 // draw highlight text
277 sect = text.substr(w->getSelectionStartIndex(), w->getSelectionLength());
278 setColourRectToSelectedTextColour(colours);
279 colours.modulateAlpha(alpha_comp);
280 text_part_rect.d_min.d_x =
281 font->drawText(w->getGeometryBuffer(), sect,
282 text_part_rect.getPosition(), &text_area, colours);
283
284 // draw post-highlight text
285 sect = text.substr(w->getSelectionEndIndex());
286 colours = unselectedColours;
287 colours.modulateAlpha(alpha_comp);
288 font->drawText(w->getGeometryBuffer(), sect, text_part_rect.getPosition(),
289 &text_area, colours);
290 }
291
292 //----------------------------------------------------------------------------//
renderTextBidi(const WidgetLookFeel & wlf,const String & text,const Rectf & text_area,float text_offset)293 void FalagardEditbox::renderTextBidi(const WidgetLookFeel& wlf,
294 const String& text,
295 const Rectf& text_area,
296 float text_offset)
297 {
298 #ifdef CEGUI_BIDI_SUPPORT
299 const Font* const font = d_window->getFont();
300
301 // setup initial rect for text formatting
302 Rectf text_part_rect(text_area);
303 // allow for scroll position
304 text_part_rect.d_min.d_x += text_offset;
305 // centre text vertically within the defined text area
306 text_part_rect.d_min.d_y += (text_area.getHeight() - font->getFontHeight()) * 0.5f;
307
308 ColourRect colours;
309 const float alpha_comp = d_window->getEffectiveAlpha();
310 // get unhighlighted text colour (saves accessing property twice)
311 ColourRect unselectedColour;
312 setColourRectToUnselectedTextColour(unselectedColour);
313 // see if the editbox is active or inactive.
314 Editbox* const w = static_cast<Editbox*>(d_window);
315 const bool active = editboxIsFocussed();
316
317 if (w->getSelectionLength() == 0)
318 {
319 // no highlighted text - we can draw the whole thing
320 colours = unselectedColour;
321 colours.modulateAlpha(alpha_comp);
322 text_part_rect.d_min.d_x =
323 font->drawText(w->getGeometryBuffer(), text,
324 text_part_rect.getPosition(), &text_area, colours);
325 }
326 else
327 {
328 // there is highlighted text - because of the Bidi support - the
329 // highlighted area can be in some cases nonconsecutive.
330 // So - we need to draw it char by char (I guess we can optimize it more
331 // but this is not that big performance hit because it only happens if
332 // we have highlighted text - not that common...)
333 for (size_t i = 0 ; i < text.size() ; i++)
334 {
335 // get the char
336 String currChar = text.substr(i, 1);
337 size_t realPos = 0;
338
339 // get he visual pos of the char
340 if (w->getBidiVisualMapping()->getV2lMapping().size() > i)
341 {
342 realPos = w->getBidiVisualMapping()->getV2lMapping()[i];
343 }
344
345 // check if it is in the highlighted region
346 bool highlighted =
347 realPos >= w->getSelectionStartIndex() &&
348 realPos < w->getSelectionStartIndex() + w->getSelectionLength();
349
350 float charAdvance = font->getGlyphData(currChar[0])->getAdvance(1.0f);
351
352 if (highlighted)
353 {
354 setColourRectToSelectedTextColour(colours);
355 colours.modulateAlpha(alpha_comp);
356
357 {
358
359 // calculate area for selection imagery.
360 Rectf hlarea(text_area);
361 hlarea.d_min.d_x = text_part_rect.d_min.d_x ;
362 hlarea.d_max.d_x = text_part_rect.d_min.d_x + charAdvance ;
363
364 // render the selection imagery.
365 wlf.getStateImagery(active ? "ActiveSelection" :
366 "InactiveSelection").
367 render(*w, hlarea, 0, &text_area);
368 }
369
370 }
371 else
372 {
373 colours = unselectedColour;
374 colours.modulateAlpha(alpha_comp);
375 }
376 font->drawText(w->getGeometryBuffer(), currChar,
377 text_part_rect.getPosition(), &text_area, colours);
378
379 // adjust rect for next section
380 text_part_rect.d_min.d_x += charAdvance;
381
382 }
383 }
384 #else
385 CEGUI_UNUSED(wlf);
386 CEGUI_UNUSED(text);
387 CEGUI_UNUSED(text_area);
388 CEGUI_UNUSED(text_offset);
389 #endif
390 }
391
392 //----------------------------------------------------------------------------//
editboxIsFocussed() const393 bool FalagardEditbox::editboxIsFocussed() const
394 {
395 Editbox* const w = static_cast<Editbox*>(d_window);
396 return w->hasInputFocus();
397 }
398
399 //----------------------------------------------------------------------------//
editboxIsReadOnly() const400 bool FalagardEditbox::editboxIsReadOnly() const
401 {
402 Editbox* const w = static_cast<Editbox*>(d_window);
403 return w->isReadOnly();
404 }
405
406 //----------------------------------------------------------------------------//
renderCaret(const ImagerySection & imagery,const Rectf & text_area,const float text_offset,const float extent_to_caret) const407 void FalagardEditbox::renderCaret(const ImagerySection& imagery,
408 const Rectf& text_area,
409 const float text_offset,
410 const float extent_to_caret) const
411 {
412 if ((!d_blinkCaret || d_showCaret) && editboxIsFocussed() && !editboxIsReadOnly())
413 {
414 Rectf caretRect(text_area);
415 caretRect.d_min.d_x += extent_to_caret + text_offset;
416
417 imagery.render(*d_window, caretRect, 0, &text_area);
418 }
419 }
420
421 //----------------------------------------------------------------------------//
getTextIndexFromPosition(const Vector2f & pt) const422 size_t FalagardEditbox::getTextIndexFromPosition(const Vector2f& pt) const
423 {
424 Editbox* w = static_cast<Editbox*>(d_window);
425
426 // calculate final window position to be checked
427 float wndx = CoordConverter::screenToWindowX(*w, pt.d_x);
428
429 wndx -= d_lastTextOffset;
430
431 // Return the proper index
432 if (w->isTextMasked())
433 return w->getFont()->getCharAtPixel(
434 String(w->getTextVisual().length(), w->getMaskCodePoint()),
435 wndx);
436 else
437 return w->getFont()->getCharAtPixel(w->getTextVisual(), wndx);
438 }
439
440 //----------------------------------------------------------------------------//
setColourRectToUnselectedTextColour(ColourRect & cr) const441 void FalagardEditbox::setColourRectToUnselectedTextColour(ColourRect& cr) const
442 {
443 setColourRectToOptionalPropertyColour(UnselectedTextColourPropertyName, cr);
444 }
445
446 //----------------------------------------------------------------------------//
setColourRectToSelectedTextColour(ColourRect & cr) const447 void FalagardEditbox::setColourRectToSelectedTextColour(ColourRect& cr) const
448 {
449 setColourRectToOptionalPropertyColour(SelectedTextColourPropertyName, cr);
450 }
451
452 //----------------------------------------------------------------------------//
setColourRectToOptionalPropertyColour(const String & propertyName,ColourRect & colour_rect) const453 void FalagardEditbox::setColourRectToOptionalPropertyColour(
454 const String& propertyName,
455 ColourRect& colour_rect) const
456 {
457 if (d_window->isPropertyPresent(propertyName))
458 colour_rect =
459 d_window->getProperty<ColourRect>(propertyName);
460 else
461 colour_rect.setColours(0);
462 }
463
464 //----------------------------------------------------------------------------//
update(float elapsed)465 void FalagardEditbox::update(float elapsed)
466 {
467 // do base class stuff
468 WindowRenderer::update(elapsed);
469
470 // only do the update if we absolutely have to
471 if (d_blinkCaret &&
472 !static_cast<Editbox*>(d_window)->isReadOnly() &&
473 static_cast<Editbox*>(d_window)->hasInputFocus())
474 {
475 d_caretBlinkElapsed += elapsed;
476
477 if (d_caretBlinkElapsed > d_caretBlinkTimeout)
478 {
479 d_caretBlinkElapsed = 0.0f;
480 d_showCaret ^= true;
481 // state changed, so need a redraw
482 d_window->invalidate();
483 }
484 }
485 }
486
487 //----------------------------------------------------------------------------//
isCaretBlinkEnabled() const488 bool FalagardEditbox::isCaretBlinkEnabled() const
489 {
490 return d_blinkCaret;
491 }
492
493 //----------------------------------------------------------------------------//
getCaretBlinkTimeout() const494 float FalagardEditbox::getCaretBlinkTimeout() const
495 {
496 return d_caretBlinkTimeout;
497 }
498
499 //----------------------------------------------------------------------------//
setCaretBlinkEnabled(bool enable)500 void FalagardEditbox::setCaretBlinkEnabled(bool enable)
501 {
502 d_blinkCaret = enable;
503 }
504
505 //----------------------------------------------------------------------------//
setCaretBlinkTimeout(float seconds)506 void FalagardEditbox::setCaretBlinkTimeout(float seconds)
507 {
508 d_caretBlinkTimeout = seconds;
509 }
510
511 //----------------------------------------------------------------------------//
setTextFormatting(const HorizontalTextFormatting format)512 void FalagardEditbox::setTextFormatting(const HorizontalTextFormatting format)
513 {
514 if (isUnsupportedFormat(format))
515 CEGUI_THROW(InvalidRequestException(
516 "currently only HTF_LEFT_ALIGNED, HTF_RIGHT_ALIGNED and "
517 "HTF_CENTRE_ALIGNED are accepted for Editbox formatting"));
518
519 d_textFormatting = format;
520 d_window->invalidate();
521 }
522
523 //----------------------------------------------------------------------------//
isUnsupportedFormat(const HorizontalTextFormatting format)524 bool FalagardEditbox::isUnsupportedFormat(const HorizontalTextFormatting format)
525 {
526 return !(format == HTF_LEFT_ALIGNED ||
527 format == HTF_RIGHT_ALIGNED ||
528 format == HTF_CENTRE_ALIGNED);
529 }
530
531 //----------------------------------------------------------------------------//
getTextFormatting() const532 HorizontalTextFormatting FalagardEditbox::getTextFormatting() const
533 {
534 return d_textFormatting;
535 }
536
537 //----------------------------------------------------------------------------//
handleFontRenderSizeChange(const Font * const font)538 bool FalagardEditbox::handleFontRenderSizeChange(const Font* const font)
539 {
540 const bool res = WindowRenderer::handleFontRenderSizeChange(font);
541
542 if (d_window->getFont() == font)
543 {
544 d_window->invalidate();
545 return true;
546 }
547
548 return res;
549 }
550
551 //----------------------------------------------------------------------------//
552
553 } // End of CEGUI namespace section
554