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