Back to index

nux  3.0.0
TextEntry.cpp
Go to the documentation of this file.
00001 /*
00002   Copyright 2008 Google Inc.
00003 
00004   Licensed under the Apache License, Version 2.0(the "License");
00005   you may not use this file except in compliance with the License.
00006   You may obtain a copy of the License at
00007 
00008        http://www.apache.org/licenses/LICENSE-2.0
00009 
00010   Unless required by applicable law or agreed to in writing, software
00011   distributed under the License is distributed on an "AS IS" BASIS,
00012   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013   See the License for the specific language governing permissions and
00014   limitations under the License.
00015 */
00016 
00017 
00018 #include "Nux.h"
00019 #include "Layout.h"
00020 #include "HLayout.h"
00021 #include "VLayout.h"
00022 #include "Validator.h"
00023 
00024 #include "cairo/cairo.h"
00025 #include "pango/pango.h"
00026 #include "pango/pangocairo.h"
00027 #include "NuxGraphics/CairoGraphics.h"
00028 
00029 #include "TextEntry.h"
00030 #include "TextEntryComposeSeqs.h"
00031 
00032 #if defined(NUX_OS_LINUX)
00033 #include <X11/cursorfont.h>
00034 #include "InputMethodIBus.h"
00035 #endif
00036 
00037 namespace nux
00038 {
00039   static const int kInnerBorderX = 2;
00040   static const int kInnerBorderY = 0; //1;
00041   static const int kCursorBlinkTimeout = 400;
00042   static const double kStrongCursorLineWidth = 1;
00043   static const double kStrongCursorBarWidth = 1;
00044   static const double kWeakCursorLineWidth = 3;
00045   static const double kWeakCursorBarWidth = 3;
00046   static const Color kStrongCursorColor(0.9f, 0.9f, 0.9f, 1.0f);
00047   static const Color kWeakCursorColor(1.0f, 1.0f, 1.0f, 0.5f);
00048   static const Color kDefaultTextColor(0, 0, 0, 1.0f);
00049   static const Color kDefaultBackgroundColor(1, 1, 1, 1.0f);
00050   static const Color kDefaultSelectionBackgroundColor(0.5, 0.5, 0.5, 1.0f);
00051   static const Color kDefaultSelectionTextColor(1, 1, 1, 1.0f);
00052   static const unsigned long long kTripleClickTimeout = 500;
00053   static const std::string kDefaultFontName = "Ubuntu";
00054 
00055   static unsigned long long GetCurrentTime()
00056   {
00057     GTimeVal tv;
00058     g_get_current_time(&tv);
00059     return static_cast<unsigned long long>(tv.tv_sec) * 1000 + tv.tv_usec / 1000;
00060   }
00061 
00062   static std::string CleanupLineBreaks(const char *source)
00063   {
00064     nuxAssert(source);
00065     std::string result;
00066     while (*source) {
00067       if (*source == '\r') {
00068         result += ' ';
00069         if (source[1] == '\n')
00070           source++;
00071       } else if (*source == '\n') {
00072         result += ' ';
00073       } else {
00074         result += *source;
00075       }
00076       source++;
00077     }
00078     return result;
00079   }
00080 
00081   // Calculate pixel size based on the Windows DPI of 96 for compatibility
00082   // reasons.
00083   CairoFont::CairoFont(const std::string &family,
00084                        /*PangoFontDescription *font,*/
00085                        double pt_size,
00086                        Style style,
00087                        Weight weight)
00088     : font_(pango_font_description_new())
00089     , size_(pt_size * PANGO_SCALE * 96 / 72)
00090     , style_(style)
00091     , weight_(weight)
00092   {
00093     pango_font_description_set_family(font_, family.c_str());
00094     pango_font_description_set_absolute_size(font_, size_);
00095 
00096     if (weight_ == CairoFont::WEIGHT_BOLD)
00097     {
00098       pango_font_description_set_weight(font_, PANGO_WEIGHT_BOLD);
00099     }
00100 
00101     if (style_ == CairoFont::STYLE_ITALIC)
00102     {
00103       pango_font_description_set_style(font_, PANGO_STYLE_ITALIC);
00104     }
00105   }
00106 
00107   CairoFont::~CairoFont()
00108   {
00109     pango_font_description_free(font_);
00110   }
00111 
00112   NUX_IMPLEMENT_OBJECT_TYPE(TextEntry);
00113 
00114   TextEntry::TextEntry(const char* text, NUX_FILE_LINE_DECL)
00115     : View(NUX_FILE_LINE_PARAM)
00116     , _size_match_text(true)
00117     , _texture2D(nullptr)
00118     , canvas_(nullptr)
00119     , cached_layout_(nullptr)
00120     , preedit_attrs_(nullptr)
00121     , completion_color_(color::Gray)
00122     , last_dblclick_time_(0)
00123     , cursor_(0)
00124     , preedit_cursor_(0)
00125     , selection_bound_(0)
00126     , scroll_offset_x_(0)
00127     , scroll_offset_y_(0)
00128     , cursor_blink_timer_(0)
00129     , cursor_blink_status_(0)
00130     , visible_(true)
00131     , focused_(false)
00132     , need_im_reset_(false)
00133     , overwrite_(false)
00134     , select_words_(false)
00135     , select_lines_(false)
00136     , button_(false)
00137     , bold_(false)
00138     , underline_(false)
00139     , strikeout_(false)
00140     , italic_(false)
00141     , multiline_(false)
00142     , wrap_(false)
00143     , cursor_visible_(false)
00144     , readonly_(false)
00145     , content_modified_(false)
00146     , selection_changed_(false)
00147     , cursor_moved_(false)
00148     , update_canvas_(true)
00149     , font_family_("Ubuntu")
00150     , font_size_(12)
00151     , font_options_(cairo_font_options_create())
00152     , font_dpi_(96.0)
00153     , _text_color(color::White)
00154     , align_(CairoGraphics::ALIGN_LEFT)
00155 #if defined(NUX_OS_LINUX)
00156     , caret_cursor_(None)
00157     , ime_(new IBusIMEContext(this))
00158 #endif
00159     , ime_active_(false)
00160     , text_input_mode_(false)
00161     , key_nav_mode_(false)
00162     , lose_key_focus_on_key_nav_direction_up_(true)
00163     , lose_key_focus_on_key_nav_direction_down_(true)
00164     , dead_key_mode_(false)
00165     , composition_mode_(false)
00166   {
00167     cairo_font_options_set_antialias(font_options_, CAIRO_ANTIALIAS_SUBPIXEL);
00168     cairo_font_options_set_hint_style(font_options_, CAIRO_HINT_STYLE_FULL);
00169     cairo_font_options_set_hint_metrics(font_options_, CAIRO_HINT_METRICS_ON);
00170     cairo_font_options_set_subpixel_order(font_options_, CAIRO_SUBPIXEL_ORDER_RGB);
00171 
00172     mouse_down.connect(sigc::mem_fun(this, &TextEntry::RecvMouseDown));
00173     mouse_drag.connect(sigc::mem_fun(this, &TextEntry::RecvMouseDrag));
00174     mouse_up.connect(sigc::mem_fun(this, &TextEntry::RecvMouseUp));
00175     mouse_double_click.connect(sigc::mem_fun(this, &TextEntry::RecvMouseDoubleClick));
00176     mouse_enter.connect(sigc::mem_fun(this, &TextEntry::RecvMouseEnter));
00177     mouse_leave.connect(sigc::mem_fun(this, &TextEntry::RecvMouseLeave));
00178 
00179     key_down.connect(sigc::mem_fun(this, &TextEntry::RecvKeyEvent));
00180     key_up.connect([this] (unsigned int keysym, unsigned long, unsigned long state) {
00181       RecvKeyEvent(NUX_KEYUP, keysym, state, nullptr, 0);
00182     });
00183 
00184     begin_key_focus.connect(sigc::mem_fun(this, &TextEntry::RecvStartKeyFocus));
00185     end_key_focus.connect(sigc::mem_fun(this, &TextEntry::RecvEndKeyFocus));
00186 
00187     SetMinimumSize(DEFAULT_WIDGET_WIDTH, PRACTICAL_WIDGET_HEIGHT);
00188     SetText(text);
00189 
00190     SetAcceptKeyboardEvent(true);
00191     EnableDoubleClick(true);
00192   }
00193 
00194   TextEntry::~TextEntry()
00195   {
00196     if (cursor_blink_timer_)
00197       g_source_remove(cursor_blink_timer_);
00198 
00199     cairo_font_options_destroy(font_options_);
00200     if (_texture2D)
00201       _texture2D->UnReference();
00202 
00203 #if defined(NUX_OS_LINUX)
00204     if (ime_)
00205       delete ime_;
00206 #endif
00207     delete canvas_;
00208     ResetLayout();
00209   }
00210 
00211   void TextEntry::PreLayoutManagement()
00212   {
00213     View::PreLayoutManagement();
00214   }
00215 
00216   long TextEntry::PostLayoutManagement(long layoutResult)
00217   {
00218     long result = View::PostLayoutManagement(layoutResult);
00219     MainDraw();
00220     return result;
00221   }
00222 
00223   void TextEntry::GeometryChanged()
00224   {
00225 
00226       update_canvas_ = true;
00227       View::GeometryChanged();
00228 
00229   }
00230 
00231   Area* TextEntry::FindAreaUnderMouse(const Point& mouse_position, NuxEventType event_type)
00232   {
00233     Area* area = View::FindAreaUnderMouse(mouse_position, event_type);
00234 
00235     return area;
00236   }
00237 
00238   void TextEntry::ProcessMouseEvent(int event_type, int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags)
00239   {
00240     int X = static_cast<int>(x /*round(event.GetX())*/) - kInnerBorderX - scroll_offset_x_;
00241     int Y = static_cast<int>(y /*round(event.GetY())*/) - kInnerBorderY - scroll_offset_y_;
00242     int index = XYToTextIndex(X, Y);
00243     MouseButton button = GetEventButton(button_flags);
00244 
00245     if (event_type == NUX_MOUSE_PRESSED && (button == 2 || button == 3))
00246     {
00247       SetCursor(index);
00248 #if defined(NUX_OS_LINUX)
00249       if (button == 2)
00250         PastePrimaryClipboard();
00251 #endif
00252       QueueRefresh(false, true);
00253 
00254       return;
00255     }
00256 
00257     if (button != 1 && event_type != NUX_MOUSE_MOVE)
00258       return;
00259 
00260     int sel_start, sel_end;
00261     GetSelectionBounds(&sel_start, &sel_end);
00262 
00263     unsigned long long current_time = GetCurrentTime();
00264 
00265     if ((event_type == NUX_MOUSE_PRESSED) && (current_time - last_dblclick_time_ <= kTripleClickTimeout))
00266     {
00267       SelectLine();
00268     }
00269     else if (event_type == NUX_MOUSE_DOUBLECLICK && !ime_active_)
00270     {
00271       SelectWord();
00272       last_dblclick_time_ = current_time;
00273     }
00274     else if (event_type == NUX_MOUSE_PRESSED)
00275     {
00276       if (key_flags & NUX_STATE_SHIFT)
00277       {
00278         // If current click position is inside the selection range, then just
00279         // cancel the selection.
00280         if (index > sel_start && index < sel_end)
00281           SetCursor(index);
00282         else if (index <= sel_start)
00283           SetSelectionBounds(sel_end, index);
00284         else if (index >= sel_end)
00285           SetSelectionBounds(sel_start, index);
00286       }
00287       else
00288       {
00289         SetCursor(index);
00290       }
00291     }
00292     else if (event_type == NUX_MOUSE_MOVE)
00293     {
00294       SetSelectionBounds(selection_bound_, index);
00295     }
00296 
00297     QueueRefresh(false, true);
00298     //return EVENT_RESULT_HANDLED;
00299   }
00300 
00301   void TextEntry::ProcessKeyEvent(
00302     unsigned long    event_type,   /*event type*/
00303     unsigned long    keysym    ,   /*event keysym*/
00304     unsigned long    state     ,   /*event state*/
00305     const char*      character ,   /*character*/
00306     unsigned short   keyCount      /*key repeat count*/)
00307   {
00308 #if defined(NUX_OS_LINUX)
00309     if (im_running())
00310     {
00311       // FIXME Have to get the current event for the x11_keycode for ibus-hangul/korean input
00312       nux::Event const& cur_event = GetGraphicsDisplay()->GetCurrentEvent();
00313       KeyEvent event(static_cast<EventType>(event_type), keysym, cur_event.x11_keycode, state, character);
00314 
00315       if (ime_->FilterKeyEvent(event))
00316         return;
00317     }
00318 #endif
00319 
00320     /* Ignore all the keyup events to make Composition and Dead keys to work,
00321      * as no one (IBus a part) needs them */
00322     if (event_type == NUX_KEYUP)
00323       return;
00324 
00325 #if defined(NUX_OS_LINUX)
00326     if (dead_key_mode_ && keysym == XK_space)
00327     {
00328       dead_key_mode_ = false;
00329       EnterText(dead_key_string_.c_str());
00330       QueueRefresh(false, true);
00331       return;
00332     }
00333 
00334     if (HandledDeadKeys(keysym, state, character))
00335     {
00336       return;
00337     }
00338     else if (HandledComposition(keysym, character))
00339     {
00340       return;
00341     }
00342 #endif
00343 
00344     if (event_type == NUX_KEYDOWN)
00345       text_input_mode_ = true;
00346 
00347     cursor_blink_status_ = 4;
00348 
00349     if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_up_) && (keysym == NUX_VK_UP))
00350     {
00351       // Ignore key navigation direction 'up' if we are not in multi-line.
00352       return;
00353     }
00354 
00355     if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_up_) && (keysym == NUX_VK_DOWN))
00356     {
00357       // Ignore key navigation direction 'down' if we are not in multi-line.
00358       return;
00359     }
00360 
00361     // we need to ignore some characters
00362     if (keysym == NUX_VK_TAB)
00363       return;
00364 
00365     if ((keysym == NUX_VK_ENTER || keysym == NUX_KP_ENTER))
00366     {
00367       activated.emit();
00368       return;
00369     }
00370 
00371     unsigned int keyval = keysym;
00372     bool shift = (state & NUX_STATE_SHIFT);
00373     bool ctrl = (state & NUX_STATE_CTRL);
00374 
00375     // DLOG("TextEntry::key_down(%d, shift:%d ctrl:%d)", keyval, shift, ctrl);
00376     if (event_type == NUX_KEYDOWN)
00377     {
00378       if (keyval == NUX_VK_LEFT)
00379       {
00380         if (!ctrl)
00381           MoveCursor(VISUALLY, -1, shift);
00382         else
00383           MoveCursor(WORDS, -1, shift);
00384       }
00385       else if (keyval == NUX_VK_RIGHT)
00386       {
00387         if (!ctrl)
00388           MoveCursor(VISUALLY, 1, shift);
00389         else
00390           MoveCursor(WORDS, 1, shift);
00391       }
00392       else if (keyval == NUX_VK_UP)
00393       {
00394         // move cursor to start of line
00395         MoveCursor(DISPLAY_LINES, -1, shift);
00396       }
00397       else if (keyval == NUX_VK_DOWN)
00398       {
00399         // move cursor to end of line
00400         MoveCursor(DISPLAY_LINES, 1, shift);
00401       }
00402       else if (keyval == NUX_VK_HOME)
00403       {
00404         if (!ctrl)
00405           MoveCursor(DISPLAY_LINE_ENDS, -1, shift);
00406         else
00407           MoveCursor(BUFFER, -1, shift);
00408       }
00409       else if (keyval == NUX_VK_END)
00410       {
00411         if (!ctrl)
00412           MoveCursor(DISPLAY_LINE_ENDS, 1, shift);
00413         else
00414           MoveCursor(BUFFER, 1, shift);
00415       }
00416       else if (keyval == NUX_VK_PAGE_UP)
00417       {
00418         if (!ctrl)
00419           MoveCursor(PAGES, -1, shift);
00420         else
00421           MoveCursor(BUFFER, -1, shift);
00422       }
00423       else if (keyval == NUX_VK_PAGE_DOWN)
00424       {
00425         if (!ctrl)
00426           MoveCursor(PAGES, 1, shift);
00427         else
00428           MoveCursor(BUFFER, 1, shift);
00429       }
00430       else if (((keyval == NUX_VK_x) && ctrl && !shift) || ((keyval == NUX_VK_DELETE) && shift && !ctrl))
00431       {
00432         CutClipboard();
00433       }
00434       else if (((keyval == NUX_VK_c) && ctrl && (!shift)) || ((keyval == NUX_VK_INSERT) && ctrl && (!shift)))
00435       {
00436         CopyClipboard();
00437       }
00438       else if (((keyval == NUX_VK_v) && ctrl && (!shift)) || ((keyval == NUX_VK_INSERT) && shift && (!ctrl)))
00439       {
00440         PasteClipboard();
00441       }
00442       else if ((keyval == NUX_VK_a) && ctrl)
00443       {
00444         SelectAll();
00445         return;
00446       }
00447       else if (keyval == NUX_VK_BACKSPACE)
00448       {
00449         if (!ctrl)
00450           BackSpace(VISUALLY);
00451         else
00452           BackSpace(WORDS);
00453       }
00454       else if ((keyval == NUX_VK_DELETE) && (!shift))
00455       {
00456         if (!ctrl)
00457           Delete(VISUALLY);
00458         else
00459           Delete(WORDS);
00460       }
00461       else if ((keyval == NUX_VK_INSERT) && (!shift) && (!ctrl))
00462       {
00463         ToggleOverwrite();
00464       }
00465 //       else
00466 //       {
00467 //         return EVENT_RESULT_UNHANDLED;
00468 //       }
00469     }
00470     else
00471     { // EVENT_KEY_PRESS
00472 //       if (keyval == GDK_Return || keyval == GDK_KP_Enter)
00473 //       {
00474 //         // If multiline_ is unset, just ignore new_line.
00475 //         if (multiline_)
00476 //           EnterText("\n");
00477 //         else
00478 //           return;
00479 //       }
00480 //       else
00481 //       {
00482 //         return;
00483 //       }
00484     }
00485 
00486     if (character)
00487     {
00488       unsigned int utf_char = g_utf8_get_char(character);
00489 
00490       if (g_unichar_isprint(utf_char))
00491         EnterText(character);
00492     }
00493 
00494     QueueRefresh(false, true);
00495   }
00496 
00497   void TextEntry::RecvMouseDoubleClick(int x, int y, unsigned long button_flags, unsigned long key_flags)
00498   {
00499     ProcessMouseEvent(NUX_MOUSE_DOUBLECLICK, x, y, 0, 0, button_flags, key_flags);
00500   }
00501 
00502   void TextEntry::RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long key_flags)
00503   {
00504     ProcessMouseEvent(NUX_MOUSE_RELEASED, x, y, 0, 0, button_flags, key_flags);
00505   }
00506 
00507   void TextEntry::RecvMouseDown(int x, int y, unsigned long button_flags, unsigned long key_flags)
00508   {
00509     ProcessMouseEvent(NUX_MOUSE_PRESSED, x, y, 0, 0, button_flags, key_flags);
00510   }
00511 
00512   void TextEntry::RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags)
00513   {
00514     ProcessMouseEvent(NUX_MOUSE_MOVE, x, y, dx, dy, button_flags, key_flags);
00515   }
00516 
00517   void TextEntry::RecvMouseEnter(int x, int y, unsigned long button_flags, unsigned long key_flags)
00518   {
00519 #if defined(NUX_OS_LINUX)
00520     if (caret_cursor_ == None)
00521     {
00522       Display* display = nux::GetGraphicsDisplay()->GetX11Display();
00523       nux::BaseWindow* window = static_cast<nux::BaseWindow*>(GetTopLevelViewWindow());
00524 
00525       if (display && window)
00526       {
00527         caret_cursor_ = XCreateFontCursor(display, XC_xterm);
00528         XDefineCursor(display, window->GetInputWindowId(), caret_cursor_);
00529       }
00530     }
00531 #endif
00532   }
00533 
00534   void TextEntry::RecvMouseLeave(int x, int y, unsigned long button_flags, unsigned long key_flags)
00535   {
00536 #if defined(NUX_OS_LINUX)
00537     if (caret_cursor_ != None)
00538     {
00539       Display* display = nux::GetGraphicsDisplay()->GetX11Display();
00540       nux::BaseWindow* window = static_cast<nux::BaseWindow*>(GetTopLevelViewWindow());
00541 
00542       if (display && window)
00543       {
00544         XUndefineCursor(display, window->GetInputWindowId());
00545         XFreeCursor(display, caret_cursor_);
00546         caret_cursor_ = None;
00547       }
00548     }
00549 #endif
00550   }
00551 
00552   void TextEntry::RecvKeyEvent(
00553     unsigned long    eventType  ,   /*event type*/
00554     unsigned long    keysym     ,   /*event keysym*/
00555     unsigned long    state      ,   /*event state*/
00556     const char*     character  ,   /*character*/
00557     unsigned short   keyCount       /*key repeat count*/)
00558   {
00559     ProcessKeyEvent(eventType, keysym, state, character, keyCount);
00560   }
00561 
00562   void TextEntry::RecvStartKeyFocus()
00563   {
00564     key_nav_mode_     = true;
00565     text_input_mode_  = false;
00566     dead_key_mode_    = false;
00567     composition_mode_ = false;
00568     composition_string_.clear();
00569 
00570     FocusInx();
00571   }
00572 
00573   void TextEntry::RecvEndKeyFocus()
00574   {
00575     key_nav_mode_     = false;
00576     text_input_mode_  = false;
00577     dead_key_mode_    = false;
00578     composition_mode_ = false;
00579     composition_string_.clear();
00580 
00581     FocusOutx();
00582   }
00583 
00584   void TextEntry::Draw(GraphicsEngine& gfxContext, bool forceDraw)
00585   {
00586     MainDraw();
00587     Geometry base = GetGeometry();
00588 
00589     gfxContext.PushClippingRectangle(base);
00590 
00591     nux::GetPainter().PaintBackground(gfxContext, base);
00592 
00593     Color col = color::Black;
00594     col.alpha = 0;
00595     gfxContext.QRP_Color(base.x,
00596       base.y,
00597       base.width,
00598       base.height,
00599       col);
00600 
00601     TexCoordXForm texxform;
00602     texxform.SetWrap(TEXWRAP_REPEAT, TEXWRAP_REPEAT);
00603     texxform.SetTexCoordType(TexCoordXForm::OFFSET_COORD);
00604     gfxContext.QRP_1Tex(base.x,
00605       base.y,
00606       base.width,
00607       base.height,
00608       _texture2D->GetDeviceTexture(),
00609       texxform,
00610       _text_color);
00611 
00612     gfxContext.PopClippingRectangle();
00613   }
00614 
00615   void TextEntry::DrawContent(GraphicsEngine& gfxContext, bool forceDraw)
00616   {
00617     //MainDraw();
00618   }
00619 
00620   void TextEntry::PostDraw(GraphicsEngine& gfxContext, bool forceDraw)
00621   {
00622     // intentionally left empty
00623   }
00624 
00625   bool TextEntry::HandledDeadKeys(int keysym, int state, const char* character)
00626   {
00627 #if defined(NUX_OS_LINUX)
00628     /* Checks if the keysym between the first and last dead key */
00629     if (character && (keysym >= XK_dead_grave) && (keysym <= XK_dead_stroke) && !dead_key_mode_)
00630     {
00631       int key = keysym - XK_dead_grave;
00632       dead_key_mode_ = true;
00633 
00634       if (dead_keys_map[key])
00635       {
00636         composition_mode_ = true;
00637         composition_string_.clear();
00638 
00639         dead_key_string_ = character;
00640 
00641         std::string dead_key;
00642         dead_key = dead_keys_map[key];
00643         HandledComposition(keysym, dead_key.c_str());
00644 
00645         return true;
00646       }
00647     }
00648     else if (dead_key_mode_ && (state & IBUS_IGNORED_MASK))
00649     {
00650       dead_key_mode_ = false;
00651     }
00652     return false;
00653 #else
00654     return false;
00655 #endif
00656   }
00657 
00658   bool TextEntry::HandledComposition(int keysym, const char* character)
00659   {
00660 #if defined(NUX_OS_LINUX)
00661     if (keysym == XK_Multi_key)
00662     {
00663       if (composition_mode_)
00664       {
00665         composition_mode_ = false;
00666       }
00667       else
00668       {
00669         composition_mode_ = true;
00670       }
00671       composition_string_.clear();
00672       return true;
00673     }
00674 
00675     if (composition_mode_ && character)
00676     {
00677       if (strncmp(character, "", 1) == 0 && keysym != NUX_VK_SHIFT)
00678       {
00679         composition_mode_ = false;
00680         composition_string_.clear();
00681         return true;
00682       }
00683 
00684       composition_string_ += character;
00685 
00686       std::string composition_match;
00687 
00688       int match = LookForMatch(composition_match);
00689 
00690       if (match == PARTIAL)
00691       {
00692         return true;
00693       }
00694       else if (match == NO_MATCH)
00695       {
00696         composition_mode_ = false;
00697         composition_string_.clear();
00698       }
00699       else if (match == MATCH)
00700       {
00701         EnterText(composition_match.c_str());
00702         composition_mode_ = false;
00703         composition_string_.clear();
00704         QueueRefresh(false, true);
00705 
00706         if (dead_key_mode_)
00707           dead_key_mode_ = false;
00708 
00709         return true;
00710       }
00711     }
00712     return false;
00713 #else
00714     return false;
00715 #endif
00716   }
00717 
00718   void TextEntry::SetText(const char* text)
00719   {
00720     const char* end = NULL;
00721     g_utf8_validate(text, -1, &end);
00722 
00723     std::string txt((text && *text && end > text) ? std::string(text, end) : "");
00724     if (txt == text_)
00725       return; // prevent some redraws
00726 
00727     text_ = multiline_ ? txt : CleanupLineBreaks(txt.c_str());
00728     cursor_ = 0;
00729     selection_bound_ = 0;
00730     need_im_reset_ = true;
00731     //ResetImContext();
00732 
00733     text_input_mode_ = true;
00734     QueueRefresh(true, true);
00735     text_changed.emit(this);
00736   }
00737 
00738   std::string const& TextEntry::GetText() const
00739   {
00740     return text_;
00741   }
00742 
00743   std::string TextEntry::GetTextSelection() const
00744   {
00745     if (text_.size() == 0)
00746     {
00747       return std::string("");
00748     }
00749 
00750     int selection_start = 0;
00751     int selection_end = 0;
00752     if (GetSelectionBounds(&selection_start, &selection_end))
00753     {
00754       return text_.substr(selection_start, selection_end);
00755     }
00756     else
00757     {
00758       return std::string("");
00759     }
00760   }
00761 
00762   void TextEntry::SetCompletion(const char* text)
00763   {
00764     const char* end = NULL;
00765     g_utf8_validate(text, -1, &end);
00766     std::string txt((text && *text && end > text) ? std::string(text, end) : "");
00767     if (txt == completion_)
00768       return;
00769 
00770     completion_ = txt;
00771     QueueRefresh(true, true);
00772   }
00773 
00774   std::string const& TextEntry::GetCompletion() const
00775   {
00776     return completion_;
00777   }
00778 
00779   void TextEntry::SetCompletionColor(const Color &color)
00780   {
00781     completion_color_ = color;
00782     QueueRefresh(true, true);
00783   }
00784 
00785   Color const& TextEntry::GetCompletionColor() const
00786   {
00787     return completion_color_;
00788   }
00789 
00790   void TextEntry::SetTextColor(const Color &text_color)
00791   {
00792     if (_text_color != text_color)
00793     {
00794       _text_color = text_color;
00795       QueueRefresh(true, true);
00796     }
00797   }
00798 
00799   Color const& TextEntry::GetTextColor() const
00800   {
00801     return _text_color;
00802   }
00803 
00804 
00805   void TextEntry::MainDraw()
00806   {
00807 
00808     CairoGraphics* edit_canvas = EnsureCanvas();
00809 
00810     if (update_canvas_ || !last_selection_region_.empty() || !selection_region_.empty())
00811     {
00812       edit_canvas->PushState();
00813       DrawText(edit_canvas);
00814       edit_canvas->PopState();
00815     }
00816 
00817 //     if (background_)
00818 //       background_->Draw(canvas, 0, 0, GetBaseWidth, GetBaseHeight);
00819 
00820     CairoGraphics* final_canvas = new CairoGraphics(CAIRO_FORMAT_ARGB32, edit_canvas->GetWidth(), edit_canvas->GetHeight());
00821 
00822     final_canvas->PushState();
00823     final_canvas->IntersectRectClipRegion(kInnerBorderX,
00824       kInnerBorderY,
00825       GetBaseWidth() - kInnerBorderX,
00826       GetBaseHeight() - kInnerBorderY);
00827     final_canvas->DrawCanvas(0, 0, edit_canvas);
00828     final_canvas->PopState();
00829     DrawCursor(final_canvas);
00830 
00831     update_canvas_ = false;
00832     last_selection_region_ = selection_region_;
00833     last_cursor_region_ = cursor_region_;
00834 
00835     NBitmapData* bitmap = final_canvas->GetBitmap();
00836     delete final_canvas;
00837 
00838     if (!_texture2D || _texture2D->GetWidth() != bitmap->GetWidth() || _texture2D->GetHeight() != bitmap->GetHeight())
00839     {
00840       if (_texture2D)
00841         _texture2D->UnReference();
00842       _texture2D = GetGraphicsDisplay()->GetGpuDevice()->CreateSystemCapableTexture();
00843     }
00844 
00845     _texture2D->Update(bitmap);
00846     delete bitmap;
00847   }
00848 
00849   void TextEntry::FocusInx()
00850   {
00851     if (!focused_)
00852     {
00853       focused_ = true;
00854       if (!readonly_ /*&& im_context_*/)
00855       {
00856         need_im_reset_ = true;
00857 #if defined(NUX_OS_LINUX)
00858         ime_->Focus();
00859 #endif
00860         //gtk_im_context_focus_in(im_context_);
00861         //UpdateIMCursorLocation();
00862       }
00863       cursor_visible_ = true; // show cursor when getting focus
00864       selection_changed_ = true;
00865       cursor_moved_ = true;
00866       // Don't adjust scroll.
00867       QueueRefresh(true, false);
00868     }
00869   }
00870 
00871   void TextEntry::FocusOutx()
00872   {
00873     if (focused_)
00874     {
00875       focused_ = false;
00876       if (!readonly_ /*&& im_context_*/)
00877       {
00878         need_im_reset_ = true;
00879 #if defined(NUX_OS_LINUX)
00880         ime_->Blur();
00881 #endif
00882         //gtk_im_context_focus_out(im_context_);
00883       }
00884       cursor_visible_ = false; // hide cursor when losing focus
00885       selection_changed_ = true;
00886       cursor_moved_ = true;
00887       // Don't adjust scroll.
00888       QueueRefresh(true, false);
00889     }
00890   }
00891 
00892   CairoGraphics* TextEntry::EnsureCanvas()
00893   {
00894     if (canvas_)
00895     {
00896       if ((GetBaseWidth() == canvas_->GetWidth()) && (GetBaseHeight() == canvas_->GetHeight()))
00897       {
00898         return canvas_;
00899       }
00900       else
00901       {
00902         nuxDebugMsg("[TextEntry::EnsureCanvas] Recreate canvas");
00903         delete canvas_;
00904         canvas_ = NULL;
00905       }
00906     }
00907     canvas_ = new CairoGraphics(CAIRO_FORMAT_ARGB32, GetBaseWidth(), GetBaseHeight());
00908     nuxAssert(canvas_);
00909     return canvas_;
00910   }
00911 
00912   void TextEntry::AdjustScroll()
00913   {
00914     int old_offset_x = scroll_offset_x_;
00915     int old_offset_y = scroll_offset_y_;
00916     int display_width = GetBaseWidth() - kInnerBorderX * 2;
00917     int display_height = GetBaseHeight() - kInnerBorderY * 2;
00918 
00919     PangoLayout* layout = EnsureLayout();
00920     int text_width, text_height;
00921     pango_layout_get_pixel_size(layout, &text_width, &text_height);
00922 
00923     int strong_x, strong_y, strong_height;
00924     int weak_x, weak_y, weak_height;
00925     GetCursorLocationInLayout(&strong_x, &strong_y, &strong_height,
00926       &weak_x, &weak_y, &weak_height);
00927 
00928     if (!wrap_ && display_width > text_width)
00929     {
00930       PangoAlignment align = pango_layout_get_alignment(layout);
00931       if (align == PANGO_ALIGN_RIGHT)
00932         scroll_offset_x_ = display_width - text_width;
00933       else if (align == PANGO_ALIGN_CENTER)
00934         scroll_offset_x_ = (display_width - text_width) / 2;
00935       else
00936         scroll_offset_x_ = 0;
00937     }
00938     else
00939     {
00940       if (scroll_offset_x_ + strong_x < 0)
00941         scroll_offset_x_ = -strong_x;
00942       else if (scroll_offset_x_ + strong_x > display_width)
00943         scroll_offset_x_ = display_width - strong_x;
00944 
00945       if (std::abs(weak_x - strong_x) < display_width)
00946       {
00947         if (scroll_offset_x_ + weak_x < 0)
00948           scroll_offset_x_ = - weak_x;
00949         else if (scroll_offset_x_ + weak_x > display_width)
00950           scroll_offset_x_ = display_width - weak_x;
00951       }
00952     }
00953 
00954     if (display_height > text_height)
00955     {
00956       scroll_offset_y_ = 0;
00957     }
00958     else
00959     {
00960       if (scroll_offset_y_ + strong_y + strong_height > display_height)
00961         scroll_offset_y_ = display_height - strong_y - strong_height;
00962       if (scroll_offset_y_ + strong_y < 0)
00963         scroll_offset_y_ = -strong_y;
00964     }
00965 
00966     if (old_offset_x != scroll_offset_x_ || old_offset_y != scroll_offset_y_)
00967       content_modified_ = true;
00968   }
00969 
00970   void TextEntry::QueueRefresh(bool relayout, bool adjust_scroll)
00971   {
00972     if (relayout)
00973       ResetLayout();
00974 
00975     if (adjust_scroll)
00976       AdjustScroll();
00977 
00978     QueueTextDraw();
00979     QueueCursorBlink();
00980   }
00981 
00982   void TextEntry::ResetImContext()
00983   {
00984     if (need_im_reset_)
00985     {
00986       need_im_reset_ = false;
00987 //       if (im_context_)
00988 //         gtk_im_context_reset(im_context_);
00989       ResetPreedit();
00990     }
00991   }
00992 
00993   void TextEntry::ResetPreedit() {
00994     // Reset layout if there were some content in preedit string
00995     if (preedit_.length())
00996       ResetLayout();
00997 
00998     preedit_.clear();
00999     preedit_cursor_ = 0;
01000     if (preedit_attrs_) {
01001       pango_attr_list_unref(preedit_attrs_);
01002       preedit_attrs_ = NULL;
01003     }
01004   }
01005 
01006   void TextEntry::DrawText(CairoGraphics *canvas)
01007   {
01008     PangoLayout *layout = EnsureLayout();
01009 
01010     bool redraw_text = false;
01011     if (update_canvas_)
01012     {
01013       canvas->ClearCanvas();
01014       canvas->PushState();
01015       redraw_text = true;
01016     }
01017     else if (!last_selection_region_.empty())
01018     {
01019       //last_selection_region_.Integerize();
01020       canvas->PushState();
01021       canvas->IntersectGeneralClipRegion(last_selection_region_);
01022       canvas->ClearRect(0, 0, GetBaseWidth(), GetBaseHeight());
01023       redraw_text = true;
01024     }
01025 
01026     if (redraw_text)
01027     {
01028       cairo_set_source_rgb(canvas->GetInternalContext(),
01029         _text_color.red,
01030         _text_color.green,
01031         _text_color.blue);
01032 
01033       cairo_move_to(canvas->GetInternalContext(),
01034         scroll_offset_x_ + kInnerBorderX,
01035         scroll_offset_y_ + kInnerBorderY);
01036 
01037       pango_cairo_show_layout(canvas->GetInternalContext(), layout);
01038 
01039       canvas->PopState();
01040     }
01041 
01042     // Draw selection background.
01043     // Selection in a single line may be not continual, so we use pango to
01044     // get the x-ranges of each selection range in one line, and draw them
01045     // separately.
01046     if (!selection_region_.empty())
01047     {
01048       canvas->PushState();
01049       //selection_region_.Integerize();
01050       canvas->IntersectGeneralClipRegion(selection_region_);
01051 
01052       Color selection_color = GetSelectionBackgroundColor();
01053       Color text_color = GetSelectionTextColor();
01054 
01055       cairo_set_source_rgb(canvas->GetInternalContext(),
01056         selection_color.red,
01057         selection_color.green,
01058         selection_color.blue);
01059       cairo_paint(canvas->GetInternalContext());
01060 
01061       cairo_move_to(canvas->GetInternalContext(),
01062         scroll_offset_x_ + kInnerBorderX,
01063         scroll_offset_y_ + kInnerBorderY);
01064       cairo_set_source_rgb(canvas->GetInternalContext(),
01065         text_color.red,
01066         text_color.green,
01067         text_color.blue);
01068       pango_cairo_show_layout(canvas->GetInternalContext(), layout);
01069       canvas->PopState();
01070     }
01071   }
01072 
01073   bool TextEntry::CursorBlinkCallback(TextEntry *self)
01074   {
01075     if (self->cursor_blink_status_)
01076       self->ShowCursor();
01077     else
01078       self->HideCursor();
01079 
01080     if (--self->cursor_blink_status_ < 0)
01081       self->cursor_blink_status_ = 2;
01082 
01083     return true;
01084   }
01085 
01086   void TextEntry::QueueCursorBlink()
01087   {
01088     if (!cursor_blink_timer_)
01089       cursor_blink_timer_ = g_timeout_add(kCursorBlinkTimeout,
01090                                           (GSourceFunc)&CursorBlinkCallback,
01091                                           this);
01092   }
01093 
01094   void TextEntry::ShowCursor()
01095   {
01096     if (!cursor_visible_)
01097     {
01098       cursor_visible_ = true;
01099       if (focused_ && !readonly_)
01100       {
01101         cursor_moved_ = true;
01102         QueueRefresh(false, false);
01103       }
01104     }
01105   }
01106 
01107   void TextEntry::HideCursor()
01108   {
01109     if (cursor_visible_)
01110     {
01111       cursor_visible_ = false;
01112       if (focused_ && !readonly_)
01113       {
01114         cursor_moved_ = true;
01115         QueueRefresh(false, false);
01116       }
01117     }
01118   }
01119 
01120   void TextEntry::GetCursorRects(Rect *strong, Rect *weak)
01121   {
01122     int strong_x, strong_y, strong_height;
01123     int weak_x, weak_y, weak_height;
01124     GetCursorLocationInLayout(&strong_x, &strong_y, &strong_height,
01125       &weak_x, &weak_y, &weak_height);
01126 
01127     strong->x =
01128       strong_x + kInnerBorderX + scroll_offset_x_ - kStrongCursorBarWidth;
01129     strong->width = kStrongCursorBarWidth * 2;
01130     strong->y = strong_y + kInnerBorderY + scroll_offset_y_;
01131     strong->height = strong_height;
01132 
01133     if (weak_x != strong_x)
01134     {
01135       weak->x = weak_x+ kInnerBorderX + scroll_offset_x_ - kWeakCursorBarWidth;
01136       weak->width = kWeakCursorBarWidth * 2;
01137       weak->y = weak_y+ kInnerBorderY + scroll_offset_y_;
01138       weak->height = weak_height;
01139     }
01140     else
01141     {
01142       *weak = *strong;
01143     }
01144   }
01145 
01146   void TextEntry::UpdateCursorRegion()
01147   {
01148     cursor_region_.clear();
01149 
01150     Rect strong, weak;
01151     GetCursorRects(&strong, &weak);
01152 
01153     cursor_region_.push_back(strong);
01154     cursor_region_.push_back(weak);
01155   }
01156 
01157 
01158   void TextEntry::DrawCursor(CairoGraphics *canvas)
01159   {
01160     if (!cursor_visible_)
01161       return;
01162 
01163     int strong_x, strong_y, strong_height;
01164     int weak_x, weak_y, weak_height;
01165     GetCursorLocationInLayout(&strong_x, &strong_y, &strong_height,
01166       &weak_x, &weak_y, &weak_height);
01167 
01168     // Draw strong cursor.
01169     // 0.5 is for cairo drawing between the grid
01170     canvas->DrawLine(strong_x + kInnerBorderX + scroll_offset_x_ + 0.5,
01171                      strong_y + kInnerBorderY + scroll_offset_y_,
01172                      strong_x + kInnerBorderX + scroll_offset_x_ + 0.5,
01173                      strong_y + strong_height + kInnerBorderY + scroll_offset_y_,
01174                      kStrongCursorLineWidth, kStrongCursorColor);
01175     // Draw a small arror towards weak cursor
01176     if (strong_x > weak_x)
01177     {
01178       canvas->DrawLine(
01179         strong_x + kInnerBorderX + scroll_offset_x_ - kStrongCursorBarWidth,
01180         strong_y + kInnerBorderY + scroll_offset_y_ + kStrongCursorLineWidth,
01181         strong_x + kInnerBorderX + scroll_offset_x_,
01182         strong_y + kInnerBorderY + scroll_offset_y_ + kStrongCursorLineWidth,
01183         kStrongCursorLineWidth, kStrongCursorColor);
01184     }
01185     else if (strong_x < weak_x)
01186     {
01187       canvas->DrawLine(
01188         strong_x + kInnerBorderX + scroll_offset_x_,
01189         strong_y + kInnerBorderY + scroll_offset_y_ + kStrongCursorLineWidth,
01190         strong_x + kInnerBorderX + scroll_offset_x_ + kStrongCursorBarWidth,
01191         strong_y + kInnerBorderY + scroll_offset_y_ + kStrongCursorLineWidth,
01192         kStrongCursorLineWidth, kStrongCursorColor);
01193     }
01194 
01195     if (strong_x != weak_x )
01196     {
01197       // Draw weak cursor.
01198       canvas->DrawLine(weak_x + kInnerBorderX + scroll_offset_x_,
01199         weak_y + kInnerBorderY + scroll_offset_y_,
01200         weak_x + kInnerBorderX + scroll_offset_x_,
01201         weak_y + weak_height + kInnerBorderY + scroll_offset_y_,
01202         kWeakCursorLineWidth, kWeakCursorColor);
01203       // Draw a small arror towards strong cursor
01204       if (weak_x > strong_x)
01205       {
01206         canvas->DrawLine(
01207           weak_x + kInnerBorderX + scroll_offset_x_ - kWeakCursorBarWidth,
01208           weak_y + kInnerBorderY + scroll_offset_y_ + kWeakCursorLineWidth,
01209           weak_x + kInnerBorderX + scroll_offset_x_,
01210           weak_y + kInnerBorderY + scroll_offset_y_ + kWeakCursorLineWidth,
01211           kWeakCursorLineWidth, kWeakCursorColor);
01212       }
01213       else
01214       {
01215         canvas->DrawLine(
01216           weak_x + kInnerBorderX + scroll_offset_x_,
01217           weak_y + kInnerBorderY + scroll_offset_y_ + kWeakCursorLineWidth,
01218           weak_x + kInnerBorderX + scroll_offset_x_ + kWeakCursorBarWidth,
01219           weak_y + kInnerBorderY + scroll_offset_y_ + kWeakCursorLineWidth,
01220           kWeakCursorLineWidth, kWeakCursorColor);
01221       }
01222     }
01223   }
01224 
01225   Color TextEntry::GetSelectionBackgroundColor()
01226   {
01227     return kDefaultSelectionBackgroundColor;
01228   }
01229 
01230   Color TextEntry::GetSelectionTextColor()
01231   {
01232     return kDefaultSelectionTextColor;
01233   }
01234 
01235   void TextEntry::GetCursorLocationInLayout(int *strong_x, int *strong_y,
01236     int *strong_height,
01237     int *weak_x, int *weak_y,
01238     int *weak_height)
01239   {
01240     PangoLayout *layout = EnsureLayout();
01241     int cursor_index = TextIndexToLayoutIndex(cursor_, true);
01242 
01243     PangoRectangle strong, weak;
01244     pango_layout_get_cursor_pos(layout, cursor_index, &strong, &weak);
01245 
01246     if (strong_x)
01247       *strong_x = PANGO_PIXELS(strong.x);
01248     if (strong_y)
01249       *strong_y = PANGO_PIXELS(strong.y);
01250     if (strong_height)
01251       *strong_height = PANGO_PIXELS(strong.height);
01252     if (weak_x)
01253       *weak_x = PANGO_PIXELS(weak.x);
01254     if (weak_y)
01255       *weak_y = PANGO_PIXELS(weak.y);
01256     if (weak_height)
01257       *weak_height = PANGO_PIXELS(weak.height);
01258   }
01259 
01260   PangoLayout* TextEntry::EnsureLayout()
01261   {
01262     if (!cached_layout_)
01263     {
01264       cached_layout_ = CreateLayout();
01265     }
01266     return cached_layout_;
01267   }
01268 
01269   int TextEntry::LookForMatch(std::string& str)
01270   {
01271     str.clear();
01272     int search_state = NO_MATCH;
01273 
01274     // Check if the string we have is a match,partial match or doesnt match
01275     for (int i = 0; nux_compose_seqs_compact[i] != "\0"; i++)
01276     {
01277       if (nux_compose_seqs_compact[i].compare(composition_string_) == 0)
01278       {
01279         // advance to the next sequence after ::
01280         while (nux_compose_seqs_compact[++i].compare("::") != 0)
01281         {
01282         }
01283 
01284         str = nux_compose_seqs_compact[++i];
01285         return MATCH;
01286       }
01287       else if (nux_compose_seqs_compact[i].find(composition_string_) != std::string::npos)
01288       {
01289         search_state = PARTIAL;
01290       }
01291     }
01292     return search_state;
01293   }
01294 
01295   void TextEntry::QueueTextDraw()
01296   {
01297     if (content_modified_)
01298     {
01299       UpdateSelectionRegion();
01300       UpdateCursorRegion();
01301       QueueDraw(); //owner->QueueDraw();
01302       content_modified_ = false;
01303       update_canvas_ = true;
01304     }
01305     else
01306     {
01307       if (selection_changed_)
01308       {
01309         UpdateSelectionRegion();
01310         if (!last_selection_region_.empty())
01311           QueueDraw(); //owner_->QueueDrawRegion(last_selection_region_);
01312         if (!selection_region_.empty())
01313           QueueDraw(); //owner_->QueueDrawRegion(selection_region_);
01314         selection_changed_ = false;
01315       }
01316       if (cursor_moved_)
01317       {
01318         UpdateCursorRegion();
01319         if (!last_cursor_region_.empty())
01320           QueueDraw(); //owner_->QueueDrawRegion(last_cursor_region_);
01321         if (!cursor_region_.empty())
01322           QueueDraw(); //owner_->QueueDrawRegion(cursor_region_);
01323         cursor_moved_ = false;
01324       }
01325     }
01326   }
01327 
01328   void TextEntry::ResetLayout()
01329   {
01330     if (cached_layout_)
01331     {
01332       g_object_unref(cached_layout_);
01333       cached_layout_ = NULL;
01334       content_modified_ = true;
01335     }
01336   }
01337 
01338   PangoLayout* TextEntry::CreateLayout()
01339   {
01340     // Creates the pango layout with a temporary canvas that is not zoomed.
01341     CairoGraphics *canvas = new CairoGraphics(CAIRO_FORMAT_ARGB32, 1, 1);
01342     PangoLayout *layout = pango_cairo_create_layout(canvas->GetInternalContext());
01343     delete canvas;
01344     PangoAttrList *tmp_attrs = pango_attr_list_new();
01345     std::string tmp_string;
01346 
01347     /* Set necessary parameters */
01348     pango_cairo_context_set_font_options(pango_layout_get_context(layout),
01349                                           font_options_);
01350     pango_cairo_context_set_resolution(pango_layout_get_context(layout),
01351                                         font_dpi_);
01352 
01353     if (wrap_)
01354     {
01355       pango_layout_set_width(layout, (GetBaseWidth() - kInnerBorderX * 2) * PANGO_SCALE);
01356       pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
01357     }
01358     else
01359     {
01360       pango_layout_set_width(layout, -1);
01361     }
01362 
01363     pango_layout_set_single_paragraph_mode(layout, !multiline_);
01364 
01365     if (preedit_.length())
01366     {
01367       size_t cursor_index = static_cast<size_t>(cursor_);
01368       size_t text_length = text_.length();
01369       size_t preedit_length = preedit_.length();
01370       if (visible_)
01371       {
01372         tmp_string = text_;
01373         tmp_string.insert(cursor_index, preedit_);
01374       }
01375       else
01376       {
01377         size_t nchars = g_utf8_strlen(text_.c_str(), text_length);
01378         size_t preedit_nchars = g_utf8_strlen(preedit_.c_str(), preedit_length);
01379         nchars += preedit_nchars;
01380         tmp_string.reserve(password_char_.length() * nchars);
01381         for (size_t i = 0; i < nchars; ++i)
01382           tmp_string.append(password_char_);
01383         size_t cursor_offset =
01384             g_utf8_pointer_to_offset(text_.c_str(), text_.c_str() + cursor_index);
01385         /* Fix cursor index and preedit_length */
01386         cursor_index = cursor_offset * password_char_.length();
01387         preedit_length = preedit_nchars * password_char_.length();
01388       }
01389       if (preedit_attrs_)
01390         pango_attr_list_splice(tmp_attrs, preedit_attrs_,
01391                                static_cast<int>(cursor_index),
01392                                static_cast<int>(preedit_length));
01393     }
01394     else
01395     {
01396       if (visible_)
01397       {
01398         tmp_string = text_;
01399       }
01400       else
01401       {
01402         size_t nchars = g_utf8_strlen(text_.c_str(), text_.length());
01403         tmp_string.reserve(password_char_.length() * nchars);
01404         for (size_t i = 0; i < nchars; ++i)
01405           tmp_string.append(password_char_);
01406       }
01407     }
01408 
01409     int pre_completion_length = tmp_string.length();
01410 
01411     if (!completion_.empty() && !wrap_ && preedit_.empty())
01412     {
01413       tmp_string = text_ + completion_;
01414     }
01415 
01416     pango_layout_set_text(layout, tmp_string.c_str(),
01417                           static_cast<int>(tmp_string.length()));
01418 
01419     /* Set necessary attributes */
01420     PangoAttribute *attr;
01421     if (underline_)
01422     {
01423       attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
01424       attr->start_index = 0;
01425       attr->end_index = static_cast<guint>(pre_completion_length);
01426       pango_attr_list_insert(tmp_attrs, attr);
01427     }
01428     if (strikeout_)
01429     {
01430       attr = pango_attr_strikethrough_new(TRUE);
01431       attr->start_index = 0;
01432       attr->end_index = static_cast<guint>(pre_completion_length);
01433       pango_attr_list_insert(tmp_attrs, attr);
01434     }
01435     if (!completion_.empty() && !wrap_)
01436     {
01437       attr = pango_attr_foreground_new(65535 * completion_color_.red,
01438                                        65535 * completion_color_.green,
01439                                        65535 * completion_color_.blue);
01440       attr->start_index = static_cast<guint>(pre_completion_length);
01441       attr->end_index = static_cast<guint>(tmp_string.length());
01442       pango_attr_list_insert(tmp_attrs, attr);
01443     }
01444     /* Set font desc */
01445     {
01446       /* safe to down_cast here, because we know the actual implementation. */
01447       CairoFont *font = new CairoFont(
01448               font_family_.empty() ? kDefaultFontName : font_family_.c_str(),
01449               font_size_,
01450               italic_ ? CairoFont::STYLE_ITALIC : CairoFont::STYLE_NORMAL,
01451               bold_ ? CairoFont::WEIGHT_BOLD : CairoFont::WEIGHT_NORMAL);
01452       nuxAssert(font);
01453       attr = pango_attr_font_desc_new(font->GetFontDescription());
01454       attr->start_index = 0;
01455       attr->end_index = static_cast<unsigned int>(tmp_string.length());
01456       pango_attr_list_insert(tmp_attrs, attr);
01457       pango_layout_set_font_description(layout, font->GetFontDescription());
01458       font->Destroy();
01459     }
01460     pango_layout_set_attributes(layout, tmp_attrs);
01461     pango_attr_list_unref(tmp_attrs);
01462 
01463     /* Set alignment according to text direction. Only set layout's alignment
01464      * when it's not wrapped and in single line mode.
01465      */
01466     if (!wrap_ && pango_layout_get_line_count(layout) <= 1 &&
01467         align_ != CairoGraphics::ALIGN_CENTER)
01468     {
01469       PangoDirection dir;
01470       if (visible_)
01471         dir = pango_find_base_dir(tmp_string.c_str(),
01472                                   static_cast<int>(tmp_string.length()));
01473       else
01474         dir = PANGO_DIRECTION_NEUTRAL;
01475 
01476       if (dir == PANGO_DIRECTION_NEUTRAL)
01477       {
01478 //         GtkWidget *widget = GetWidgetAndCursorLocation(NULL);
01479 //         if (widget && gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
01480 //           dir = PANGO_DIRECTION_RTL;
01481 //         else
01482 //           dir = PANGO_DIRECTION_LTR;
01483 
01484         dir = PANGO_DIRECTION_LTR;
01485       }
01486 
01487       // If wordWrap is false then "justify" alignment has no effect.
01488       PangoAlignment pango_align = (align_ == CairoGraphics::ALIGN_RIGHT ?
01489                                     PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT);
01490 
01491       // Invert the alignment if text direction is right to left.
01492       if (dir == PANGO_DIRECTION_RTL)
01493       {
01494         pango_align = (align_ == CairoGraphics::ALIGN_RIGHT ?
01495                        PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT);
01496       }
01497 
01498       pango_layout_set_alignment(layout, pango_align);
01499       pango_layout_set_justify(layout, FALSE);
01500     }
01501     else if (align_ == CairoGraphics::ALIGN_JUSTIFY)
01502     {
01503       pango_layout_set_justify(layout, TRUE);
01504       pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
01505     }
01506     else if (align_ == CairoGraphics::ALIGN_RIGHT)
01507     {
01508       pango_layout_set_justify(layout, FALSE);
01509       pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
01510     }
01511     else if (align_ == CairoGraphics::ALIGN_CENTER)
01512     {
01513       pango_layout_set_justify(layout, FALSE);
01514       pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
01515     }
01516     else
01517     {
01518       pango_layout_set_justify(layout, FALSE);
01519       pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
01520     }
01521 
01522     {
01523       PangoContext *context;
01524       PangoFontMetrics *metrics;
01525       int ascent, descent;
01526 
01527       context = pango_layout_get_context(layout);
01528       metrics = pango_context_get_metrics(context,
01529                                            pango_layout_get_font_description(layout),
01530                                            pango_context_get_language(context));
01531 
01532       ascent = pango_font_metrics_get_ascent(metrics);
01533       descent = pango_font_metrics_get_descent(metrics);
01534 
01535       int full_height = PANGO_PIXELS(ascent + descent) + (kInnerBorderY * 2);
01536       SetMinimumHeight(full_height);
01537 
01538       pango_font_metrics_unref(metrics);
01539     }
01540 
01541     return layout;
01542   }
01543 
01544   int TextEntry::TextIndexToLayoutIndex(int text_index, bool consider_preedit_cursor)
01545   {
01546       if (visible_)
01547       {
01548         if (text_index < cursor_)
01549           return text_index;
01550 
01551         if (text_index == cursor_ && consider_preedit_cursor)
01552           return text_index + preedit_cursor_;
01553 
01554         return text_index + static_cast<int>(preedit_.length());
01555       }
01556 
01557       const char *text = text_.c_str();
01558       int offset = static_cast<int>(
01559         g_utf8_pointer_to_offset(text, text + text_index));
01560       int preedit_offset = 0;
01561       int preedit_chars = 0;
01562       if (preedit_.length())
01563       {
01564         const char *preedit_text = preedit_.c_str();
01565         preedit_offset = static_cast<int>(g_utf8_pointer_to_offset(
01566           preedit_text, preedit_text + preedit_cursor_));
01567         preedit_chars = static_cast<int>(g_utf8_strlen(
01568           preedit_text, preedit_.length()));
01569       }
01570 
01571       int password_char_length = static_cast<int>(password_char_.length());
01572 
01573       if (text_index < cursor_)
01574         return offset * password_char_length;
01575 
01576       if (text_index == cursor_ && consider_preedit_cursor)
01577         return (offset + preedit_offset) * password_char_length;
01578 
01579       return (offset + preedit_chars) * password_char_length;
01580   }
01581 
01582 
01583   int TextEntry::LayoutIndexToTextIndex(int layout_index)
01584   {
01585     if (visible_)
01586     {
01587       if (layout_index < cursor_)
01588         return layout_index;
01589 
01590       int preedit_length = static_cast<int>(preedit_.length());
01591       if (layout_index >= cursor_ + preedit_length)
01592         return layout_index - preedit_length;
01593 
01594       return cursor_;
01595     }
01596 
01597     int password_char_length = static_cast<int>(password_char_.length());
01598     nuxAssert(layout_index % password_char_length == 0);
01599 
01600     int offset = layout_index / password_char_length;
01601 
01602     const char *text = text_.c_str();
01603     int cursor_offset = static_cast<int>(
01604       g_utf8_pointer_to_offset(text, text + cursor_));
01605     int preedit_chars = static_cast<int>(
01606       g_utf8_strlen(preedit_.c_str(), preedit_.length()));
01607 
01608     if (offset < cursor_offset)
01609       return static_cast<int>(g_utf8_offset_to_pointer(text, offset) - text);
01610 
01611     if (offset >= cursor_offset + preedit_chars)
01612       return static_cast<int>(
01613       g_utf8_offset_to_pointer(text, offset - preedit_chars) - text);
01614 
01615     return cursor_;
01616   }
01617 
01618   int TextEntry::GetCharLength(int index)
01619   {
01620     const char *text = text_.c_str();
01621     const char *ptr = text + index;
01622     const char *end = text + text_.length();
01623     const char *next = g_utf8_find_next_char(ptr, end);
01624     return static_cast<int>(next ? static_cast<int>(next - ptr) : end - ptr);
01625   }
01626 
01627   int TextEntry::GetPrevCharLength(int index)
01628   {
01629     const char *text = text_.c_str();
01630     const char *ptr = text + index;
01631     const char *prev = g_utf8_find_prev_char(text, ptr);
01632     return static_cast<int>(prev ? static_cast<int>(ptr - prev) : ptr - text);
01633   }
01634 
01635   void TextEntry::EnterText(const char *str)
01636   {
01637     if (readonly_ || !str || !*str) return;
01638 
01639     if (GetSelectionBounds(NULL, NULL))
01640     {
01641       DeleteSelection();
01642     }
01643     else if (overwrite_ && cursor_ != static_cast<int>(text_.length()))
01644     {
01645       DeleteText(cursor_, cursor_ + GetCharLength(cursor_));
01646     }
01647 
01648     std::string tmp_text;
01649     if (!multiline_)
01650     {
01651       tmp_text = CleanupLineBreaks(str);
01652       str = tmp_text.c_str();
01653     }
01654 
01655     const char *end = NULL;
01656     g_utf8_validate(str, -1, &end);
01657     if (end > str)
01658     {
01659       size_t len = end - str;
01660 
01661       text_.insert(cursor_, str, len);
01662       cursor_ += static_cast<int>(len);
01663       selection_bound_ += static_cast<int>(len);
01664     }
01665 
01666     ResetLayout();
01667     text_input_mode_ = true;
01668     text_changed.emit(this);
01669   }
01670 
01671   void TextEntry::DeleteText(int start, int end)
01672   {
01673     if (readonly_) return;
01674 
01675     int text_length = static_cast<int>(text_.length());
01676     if (start < 0)
01677       start = 0;
01678     else if (start > text_length)
01679       start = text_length;
01680 
01681     if (end < 0)
01682       end = 0;
01683     else if (end > text_length)
01684       end = text_length;
01685 
01686     if (start > end)
01687       std::swap(start, end);
01688     else if (start == end)
01689       return;
01690 
01691     text_.erase(start, end - start);
01692 
01693     if (cursor_ >= end)
01694       cursor_ -= (end - start);
01695     if (selection_bound_ >= end)
01696       selection_bound_ -= (end - start);
01697 
01698     ResetLayout();
01699     text_input_mode_ = true;
01700     text_changed.emit(this);
01701   }
01702 
01703   void TextEntry::SelectWord()
01704   {
01705     int selection_bound = MoveWords(cursor_, -1);
01706     int cursor = MoveWords(selection_bound, 1);
01707     SetSelectionBounds(selection_bound, cursor);
01708   }
01709 
01710   void TextEntry::SelectLine()
01711   {
01712     int selection_bound = MoveLineEnds(cursor_, -1);
01713     int cursor = MoveLineEnds(selection_bound, 1);
01714     SetSelectionBounds(selection_bound, cursor);
01715   }
01716 
01717   void TextEntry::Select(int start, int end) {
01718     int text_length = static_cast<int>(text_.length());
01719     if (start == -1)
01720       start = text_length;
01721     if (end == -1)
01722       end = text_length;
01723 
01724     start = Clamp(start, 0, text_length);
01725     end = Clamp(end, 0, text_length);
01726     SetSelectionBounds(start, end);
01727     QueueRefresh(false, true);
01728   }
01729 
01730   void TextEntry::SelectAll() {
01731     SetSelectionBounds(0, static_cast<int>(text_.length()));
01732     QueueRefresh(false, true);
01733   }
01734 
01735   CairoGraphics::Alignment TextEntry::GetAlign() const
01736   {
01737     return align_;
01738   }
01739 
01740   void TextEntry::SetAlign(CairoGraphics::Alignment align)
01741   {
01742     align_ = align;
01743     QueueRefresh(true, true);
01744   }
01745 
01746   bool TextEntry::im_active()
01747   {
01748     return ime_active_;
01749   }
01750 
01751   bool TextEntry::im_running()
01752   {
01753 #if defined(NUX_OS_LINUX)
01754     return ime_->IsConnected();
01755 #else
01756     return false;
01757 #endif
01758   }
01759 
01760   void TextEntry::DeleteSelection()
01761   {
01762     int start, end;
01763     if (GetSelectionBounds(&start, &end))
01764       DeleteText(start, end);
01765   }
01766 
01767   void TextEntry::CopyClipboard()
01768   {
01769 //     int start, end;
01770 //     if (GetSelectionBounds(&start, &end))
01771 //     {
01772 //       GtkWidget *widget = GetWidgetAndCursorLocation(NULL);
01773 //       if (widget)
01774 //       {
01775 //         if (visible_)
01776 //         {
01777 //           gtk_clipboard_set_text(
01778 //             gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD),
01779 //             text_.c_str() + start, end - start);
01780 //         }
01781 //         else
01782 //         {
01783 //           // Don't copy real content if it's in invisible.
01784 //           std::string content;
01785 //           int nchars = static_cast<int>(
01786 //             g_utf8_strlen(text_.c_str() + start, end - start));
01787 //           for (int i = 0; i < nchars; ++i)
01788 //             content.append(password_char_);
01789 //           gtk_clipboard_set_text(
01790 //             gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD),
01791 //             content.c_str(), static_cast<int>(content.length()));
01792 //         }
01793 //       }
01794 //     }
01795   }
01796 
01797   void TextEntry::CutClipboard()
01798   {
01799     CopyClipboard();
01800     DeleteSelection();
01801     QueueRefresh(true, true);
01802   }
01803 
01804   void TextEntry::PasteClipboard()
01805   {
01806 //     GtkWidget *widget = GetWidgetAndCursorLocation(NULL);
01807 //     if (widget)
01808 //     {
01809 //       gtk_clipboard_request_text(
01810 //         gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD),
01811 //         PasteCallback, this);
01812 //     }
01813   }
01814 
01815 #if defined(NUX_OS_LINUX)
01816   void TextEntry::PastePrimaryClipboard()
01817   {
01818     // TODO
01819   }
01820 #endif
01821 
01822   void TextEntry::BackSpace(MovementStep step)
01823   {
01824     if (GetSelectionBounds(NULL, NULL))
01825     {
01826       DeleteSelection();
01827     }
01828     else
01829     {
01830       if (cursor_ == 0)
01831         return;
01832       if (step == VISUALLY)
01833       {
01834         DeleteText(cursor_ - GetPrevCharLength(cursor_), cursor_);
01835       }
01836       else if (step == WORDS)
01837       {
01838         int new_cursor;
01839         new_cursor = MoveWords(cursor_, -1);
01840         DeleteText(new_cursor, cursor_);
01841       }
01842     }
01843   }
01844 
01845   void TextEntry::Delete(MovementStep step)
01846   {
01847     if (GetSelectionBounds(NULL, NULL))
01848     {
01849       DeleteSelection();
01850     }
01851     else
01852     {
01853       if (cursor_ == static_cast<int>(text_.length()))
01854         return;
01855       if (step == VISUALLY)
01856       {
01857         DeleteText(cursor_, cursor_ + GetCharLength(cursor_));
01858       }
01859       else if (step == WORDS)
01860       {
01861         int new_cursor;
01862         new_cursor = MoveWords(cursor_, 1);
01863         DeleteText(cursor_, new_cursor);
01864       }
01865     }
01866   }
01867 
01868   void TextEntry::ToggleOverwrite()
01869   {
01870     overwrite_ = !overwrite_;
01871   }
01872 
01873   void TextEntry::UpdateSelectionRegion()
01874   {
01875     selection_region_.clear();
01876 
01877     // Selection in a single line may be not continual, so we use pango to
01878     // get the x-ranges of each selection range in one line, and draw them
01879     // separately.
01880     int start_index, end_index;
01881     if (GetSelectionBounds(&start_index, &end_index))
01882     {
01883       PangoLayout *layout = EnsureLayout();
01884       PangoRectangle line_extents, pos;
01885       int draw_start, draw_end;
01886       int *ranges;
01887       int n_ranges;
01888       int n_lines = pango_layout_get_line_count(layout);
01889 
01890       start_index = TextIndexToLayoutIndex(start_index, false);
01891       end_index = TextIndexToLayoutIndex(end_index, false);
01892 
01893       for (int line_index = 0; line_index < n_lines; ++line_index)
01894       {
01895 #if PANGO_VERSION_CHECK(1,16,0)
01896         PangoLayoutLine *line = pango_layout_get_line_readonly(layout, line_index);
01897 #else
01898         PangoLayoutLine *line = pango_layout_get_line(layout, line_index);
01899 #endif
01900         if (line->start_index + line->length < start_index)
01901           continue;
01902         if (end_index < line->start_index)
01903           break;
01904         draw_start = std::max<int>(start_index, line->start_index);
01905         draw_end = std::min<int>(end_index, line->start_index + line->length);
01906         pango_layout_line_get_x_ranges(line, draw_start, draw_end,
01907           &ranges, &n_ranges);
01908         pango_layout_line_get_pixel_extents(line, NULL, &line_extents);
01909         pango_layout_index_to_pos(layout, line->start_index,  &pos);
01910         for (int i = 0; i < n_ranges; ++i)
01911         {
01912           selection_region_.push_back(Rect(
01913             kInnerBorderX + scroll_offset_x_ + PANGO_PIXELS(ranges[i * 2]),
01914             kInnerBorderY + scroll_offset_y_ + PANGO_PIXELS(pos.y),
01915             PANGO_PIXELS(ranges[i * 2 + 1] - ranges[i * 2]),
01916             line_extents.height));
01917         }
01918         g_free(ranges);
01919       }
01920     }
01921   }
01922 
01923   void TextEntry::MoveCursor(MovementStep step, int count, bool extend_selection)
01924   {
01925     ResetImContext();
01926     int new_cursor = 0;
01927     // Clear selection first if not extend it.
01928     if (!extend_selection)
01929     {
01930       selection_changed_ = true;
01931       cursor_moved_ = true;
01932       selection_bound_ = cursor_;
01933       cursor_moved.emit(cursor_);
01934     }
01935 
01936     // Calculate the new offset after motion.
01937     switch(step)
01938     {
01939     case VISUALLY:
01940       new_cursor = MoveVisually(cursor_, count);
01941       break;
01942     case WORDS:
01943       new_cursor = MoveWords(cursor_, count);
01944       break;
01945     case DISPLAY_LINES:
01946       new_cursor = MoveDisplayLines(cursor_, count);
01947       break;
01948     case DISPLAY_LINE_ENDS:
01949       new_cursor = MoveLineEnds(cursor_, count);
01950       break;
01951     case PAGES:
01952       new_cursor = MovePages(cursor_, count);
01953       break;
01954     case BUFFER:
01955       nuxAssert(count == -1 || count == 1);
01956       new_cursor = static_cast<int>(count == -1 ? 0 : text_.length());
01957       break;
01958     }
01959 
01960     if (extend_selection)
01961       SetSelectionBounds(selection_bound_, new_cursor);
01962     else
01963       SetCursor(new_cursor);
01964 
01965     QueueRefresh(true, true);
01966   }
01967 
01968   int TextEntry::MoveVisually(int current_index, int count)
01969   {
01970     nuxAssert(current_index >= 0 &&
01971       current_index <= static_cast<int>(text_.length()));
01972     nuxAssert(count);
01973     nuxAssert(preedit_.length() == 0);
01974 
01975     PangoLayout* layout = EnsureLayout();
01976     const char* text = pango_layout_get_text(layout);
01977     int index = TextIndexToLayoutIndex(current_index, false);
01978     int new_index = 0;
01979     int new_trailing = 0;
01980     while (count != 0)
01981     {
01982       if (count > 0)
01983       {
01984         --count;
01985         pango_layout_move_cursor_visually(layout, true, index, 0, 1,
01986           &new_index, &new_trailing);
01987       }
01988       else if (count < 0)
01989       {
01990         ++count;
01991         pango_layout_move_cursor_visually(layout, true, index, 0, -1,
01992           &new_index, &new_trailing);
01993       }
01994       index = new_index;
01995       if (index < 0 || index == G_MAXINT)
01996         return current_index;
01997       index = static_cast<int>(g_utf8_offset_to_pointer(text + index, new_trailing) - text);
01998     }
01999     return LayoutIndexToTextIndex(index);
02000   }
02001 
02002   int TextEntry::MoveWords(int current_index, int count)
02003   {
02004     nuxAssert(current_index >= 0 &&
02005       current_index <= static_cast<int>(text_.length()));
02006     nuxAssert(count);
02007     nuxAssert(preedit_.length() == 0);
02008 
02009     if (!visible_)
02010     {
02011       return static_cast<int>(count > 0 ? text_.length() : 0);
02012     }
02013 
02014     // The cursor movement direction shall be determined by the direction of
02015     // current text line.
02016     PangoLayout* layout = EnsureLayout();
02017     int n_log_attrs;
02018     PangoLogAttr* log_attrs;
02019     pango_layout_get_log_attrs(layout, &log_attrs, &n_log_attrs);
02020     const char* text = pango_layout_get_text(layout);
02021     int index = TextIndexToLayoutIndex(current_index, false);
02022     int line_index;
02023     pango_layout_index_to_line_x(layout, index, FALSE, &line_index, NULL);
02024 
02025     // Weird bug: line_index here may be >= than line count?
02026     int line_count = pango_layout_get_line_count(layout);
02027     if (line_index >= line_count)
02028     {
02029       line_index = line_count - 1;
02030     }
02031 
02032 #if PANGO_VERSION_CHECK(1,16,0)
02033     PangoLayoutLine* line = pango_layout_get_line_readonly(layout, line_index);
02034 #else
02035     PangoLayoutLine* line = pango_layout_get_line(layout, line_index);
02036 #endif
02037     bool rtl = (line->resolved_dir == PANGO_DIRECTION_RTL);
02038     const char* ptr = text + index;
02039     int offset = static_cast<int>(g_utf8_pointer_to_offset(text, ptr));
02040     while (count != 0)
02041     {
02042       if (((rtl && count < 0) || (!rtl && count > 0)) && *ptr)
02043       {
02044         if (log_attrs[offset].is_white)
02045         {
02046           while (ptr && *ptr && log_attrs[offset].is_white)
02047           {
02048             ptr = g_utf8_find_next_char(ptr, NULL);
02049             ++offset;
02050           }
02051         }
02052         else
02053         {
02054           if (ptr && *ptr)
02055           {
02056             ptr = g_utf8_find_next_char(ptr, NULL);
02057             ++offset;
02058           }
02059         }
02060         while (ptr && *ptr)
02061         {
02062           ptr = g_utf8_find_next_char(ptr, NULL);
02063           ++offset;
02064           if (log_attrs[offset].is_word_start || log_attrs[offset].is_word_end)
02065             break;
02066         }
02067         if (!ptr)
02068         {
02069           ptr = text;
02070           while (*ptr) ++ptr;
02071         }
02072       }
02073       else if (((rtl && count > 0) || (!rtl && count < 0)) && (ptr > text))
02074       {
02075         if (offset > 0 && log_attrs[offset - 1].is_white)
02076         {
02077           while (ptr && offset > 0 && log_attrs[offset - 1].is_white)
02078           {
02079             ptr = g_utf8_find_prev_char(text, ptr);
02080             --offset;
02081           }
02082         }
02083         else
02084         {
02085           if (ptr)
02086           {
02087             ptr = g_utf8_find_prev_char(text, ptr);
02088             --offset;
02089           }
02090         }
02091         while (ptr /*&& *ptr*/) //fix: when at the end of the string, allow ctrl+left arrow to move backward to the start/end of the previous word.
02092         {
02093           ptr = g_utf8_find_prev_char(text, ptr);
02094           --offset;
02095           if (log_attrs[offset].is_word_start || log_attrs[offset].is_word_end)
02096             break;
02097         }
02098         if (!ptr)
02099           ptr = text;
02100       }
02101       else
02102       {
02103         break;
02104       }
02105       if (count > 0)
02106         --count;
02107       else
02108         ++count;
02109     }
02110     return LayoutIndexToTextIndex(static_cast<int>(ptr - text));
02111   }
02112 
02113   int TextEntry::MoveDisplayLines(int current_index, int count)
02114   {
02115     nuxAssert(current_index >= 0 &&
02116       current_index <= static_cast<int>(text_.length()));
02117     nuxAssert(count);
02118     nuxAssert(preedit_.length() == 0);
02119 
02120     PangoLayout *layout = EnsureLayout();
02121     const char *text = pango_layout_get_text(layout);
02122     int index = TextIndexToLayoutIndex(current_index, false);
02123     int n_lines = pango_layout_get_line_count(layout);
02124     int line_index = 0;
02125     int x_off = 0;
02126     PangoRectangle rect;
02127 
02128     // Find the current cursor X position in layout
02129     pango_layout_index_to_line_x(layout, index, FALSE, &line_index, &x_off);
02130 
02131     // Weird bug: line_index here may be >= than line count?
02132     if (line_index >= n_lines)
02133     {
02134       line_index = n_lines - 1;
02135     }
02136 
02137     pango_layout_get_cursor_pos(layout, index, &rect, NULL);
02138     x_off = rect.x;
02139 
02140     line_index += count;
02141 
02142     if (line_index < 0)
02143     {
02144       return 0;
02145     }
02146     else if (line_index >= n_lines)
02147     {
02148       return static_cast<int>(text_.length());
02149     }
02150 
02151     int trailing;
02152 #if PANGO_VERSION_CHECK(1,16,0)
02153     PangoLayoutLine *line = pango_layout_get_line_readonly(layout, line_index);
02154 #else
02155     PangoLayoutLine *line = pango_layout_get_line(layout, line_index);
02156 #endif
02157     // Find out the cursor x offset related to the new line position.
02158     if (line->resolved_dir == PANGO_DIRECTION_RTL)
02159     {
02160       pango_layout_get_cursor_pos(layout, line->start_index + line->length,
02161         &rect, NULL);
02162     }
02163     else
02164     {
02165       pango_layout_get_cursor_pos(layout, line->start_index, &rect, NULL);
02166     }
02167 
02168     // rect.x is the left edge position of the line in the layout
02169     x_off -= rect.x;
02170     if (x_off < 0) x_off = 0;
02171     pango_layout_line_x_to_index(line, x_off, &index, &trailing);
02172 
02173     index = static_cast<int>(g_utf8_offset_to_pointer(text + index, trailing) - text);
02174     return LayoutIndexToTextIndex(index);
02175   }
02176 
02177   int TextEntry::MovePages(int current_index, int count)
02178   {
02179     nuxAssert(current_index >= 0 &&
02180       current_index <= static_cast<int>(text_.length()));
02181     nuxAssert(count);
02182     nuxAssert(preedit_.length() == 0);
02183 
02184     // Transfer pages to display lines.
02185     PangoLayout *layout = EnsureLayout();
02186     int layout_height;
02187     pango_layout_get_pixel_size(layout, NULL, &layout_height);
02188     int n_lines = pango_layout_get_line_count(layout);
02189     int line_height = layout_height / n_lines;
02190     int page_lines = (GetBaseHeight() - kInnerBorderY * 2) / line_height;
02191     return MoveDisplayLines(current_index, count * page_lines);
02192   }
02193 
02194   int TextEntry::MoveLineEnds(int current_index, int count)
02195   {
02196     nuxAssert(current_index >= 0 &&
02197       current_index <= static_cast<int>(text_.length()));
02198     nuxAssert(count);
02199     nuxAssert(preedit_.length() == 0);
02200 
02201     PangoLayout *layout = EnsureLayout();
02202     int index = TextIndexToLayoutIndex(current_index, false);
02203     int line_index = 0;
02204 
02205     // Find current line
02206     pango_layout_index_to_line_x(layout, index, FALSE, &line_index, NULL);
02207 
02208     // Weird bug: line_index here may be >= than line count?
02209     int line_count = pango_layout_get_line_count(layout);
02210     if (line_index >= line_count)
02211     {
02212       line_index = line_count - 1;
02213     }
02214 
02215 // #if PANGO_VERSION_CHECK(1,16,0)
02216 //     PangoLayoutLine *line = pango_layout_get_line_readonly(layout, line_index);
02217 // #else
02218     PangoLayoutLine *line = pango_layout_get_line(layout, line_index);
02219 // #endif
02220 
02221     if (line->length == 0)
02222       return current_index;
02223 
02224     if ((line->resolved_dir == PANGO_DIRECTION_RTL && count < 0) ||
02225       (line->resolved_dir != PANGO_DIRECTION_RTL && count > 0))
02226     {
02227         index = line->start_index + line->length;
02228     }
02229     else
02230     {
02231       index = line->start_index;
02232     }
02233     return LayoutIndexToTextIndex(index);
02234   }
02235 
02236   void TextEntry::SetCursor(int cursor)
02237   {
02238     if (cursor != cursor_)
02239     {
02240       ResetImContext();
02241       // If there was a selection range, then the selection range will be cleared.
02242       // Then content_modified_ shall be set to true to force redrawing the text.
02243       if (cursor_ != selection_bound_)
02244         selection_changed_ = true;
02245       cursor_ = cursor;
02246       selection_bound_ = cursor;
02247       cursor_moved_ = true;
02248 
02249       cursor_moved.emit(cursor);
02250     }
02251   }
02252 
02253   int TextEntry::XYToTextIndex(int x, int y)
02254   {
02255     int width, height;
02256     PangoLayout *layout = EnsureLayout();
02257     const char *text = pango_layout_get_text(layout);
02258     pango_layout_get_pixel_size(layout, &width, &height);
02259 
02260     if (y < 0)
02261     {
02262       return 0;
02263     }
02264     else if (y >= height)
02265     {
02266       return static_cast<int>(text_.length());
02267     }
02268 
02269     int trailing;
02270     int index;
02271     pango_layout_xy_to_index(layout, x * PANGO_SCALE, y * PANGO_SCALE,
02272       &index, &trailing);
02273     index = static_cast<int>(
02274       g_utf8_offset_to_pointer(text + index, trailing) - text);
02275 
02276     index = LayoutIndexToTextIndex(index);
02277 
02278     // Adjust the offset if preedit is not empty and if the offset is after
02279     // current cursor.
02280     int preedit_length = static_cast<int>(preedit_.length());
02281     if (preedit_length && index > cursor_)
02282     {
02283       if (index >= cursor_ + preedit_length)
02284         index -= preedit_length;
02285       else
02286         index = cursor_;
02287     }
02288     return Clamp(index, 0, static_cast<int>(text_.length()));
02289   }
02290 
02291   bool TextEntry::GetSelectionBounds(int* start, int* end) const
02292   {
02293     if (start)
02294     {
02295       *start = std::min<int>(selection_bound_, cursor_);
02296     }
02297 
02298     if (end)
02299     {
02300       *end = std::max<int>(selection_bound_, cursor_);
02301     }
02302 
02303     return (selection_bound_ != cursor_);
02304   }
02305 
02306   void TextEntry::SetSelectionBounds(int selection_bound, int cursor)
02307   {
02308     if (selection_bound_ != selection_bound || cursor_ != cursor)
02309     {
02310       selection_changed_ = true;
02311       selection_bound_ = selection_bound;
02312       if (cursor_ != cursor)
02313       {
02314         cursor_ = cursor;
02315         cursor_moved_ = true;
02316         cursor_moved.emit(cursor);
02317       }
02318 
02319       //ResetImContext();
02320     }
02321   }
02322 
02323   void TextEntry::SetFontFamily(const char *font)
02324   {
02325     font_family_ = font;
02326     QueueRefresh(true, true);
02327   }
02328 
02329   void TextEntry::SetFontSize(double font_size)
02330   {
02331     font_size_ = font_size;
02332     QueueRefresh(true, true);
02333   }
02334 
02335   void TextEntry::SetFontOptions(const cairo_font_options_t *options)
02336   {
02337     g_return_if_fail(options);
02338 
02339     cairo_font_options_destroy(font_options_);
02340     font_options_ = cairo_font_options_copy(options);
02341 
02342     QueueRefresh(true, true);
02343   }
02344 
02345   bool TextEntry::InspectKeyEvent(unsigned int eventType, unsigned int key_sym, const char* character)
02346   {
02347     nux::Event const& cur_event = GetGraphicsDisplay()->GetCurrentEvent();
02348 
02349     return InspectKeyEvent(cur_event);
02350   }
02351 
02352   bool TextEntry::InspectKeyEvent(nux::Event const& event)
02353   {
02354     unsigned int eventType = event.type;
02355     unsigned int key_sym = event.GetKeySym();
02356 
02357 #if defined(NUX_OS_LINUX)
02358     if (im_running())
02359     {
02360       // Always allow IBus hotkey events
02361       if (ime_->IsHotkeyEvent(static_cast<EventType>(eventType), key_sym, event.key_modifiers))
02362         return true;
02363     }
02364 #endif
02365 
02366     // Ignore events when Alt or Super are pressed
02367     if (event.GetKeyModifierState(KEY_MODIFIER_SUPER) || event.GetKeyModifierState(KEY_MODIFIER_ALT))
02368       return false;
02369 
02370     if ((eventType == NUX_KEYDOWN) && (key_nav_mode_ == true) && (text_input_mode_ == false) && (ime_active_ == false))
02371     {
02372       if (key_sym == NUX_VK_ENTER ||
02373         key_sym == NUX_KP_ENTER ||
02374         key_sym == NUX_VK_UP ||
02375         key_sym == NUX_VK_DOWN ||
02376         key_sym == NUX_VK_LEFT ||
02377         key_sym == NUX_VK_RIGHT ||
02378         key_sym == NUX_VK_LEFT_TAB ||
02379         key_sym == NUX_VK_TAB ||
02380         key_sym == NUX_VK_ESCAPE)
02381       {
02382         if (key_sym == NUX_VK_LEFT ||
02383             key_sym == NUX_VK_RIGHT ||
02384             key_sym == NUX_VK_ENTER ||
02385             key_sym == NUX_KP_ENTER)
02386         {
02387           return true;
02388         }
02389 
02390         if (multiline_ && (key_sym == NUX_VK_UP))
02391         {
02392           // Navigate between the lines of the text entry.
02393           // This will move the cursor one line up.
02394           return true;
02395         }
02396 
02397         if (multiline_ && (key_sym == NUX_VK_DOWN))
02398         {
02399           // Navigate between the lines of the text entry.
02400           // This will move the cursor one line down.
02401           return true;
02402         }
02403 
02404         if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_up_) && (key_sym == NUX_VK_UP))
02405         {
02406           // By returning true, the text entry signals that it want to receive the signal for this event.
02407           // Otherwise, the parent view of the text entry would be looking for another view to receive keynav focus to.
02408           return true;
02409         }
02410 
02411         if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_down_) && (key_sym == NUX_VK_DOWN))
02412         {
02413           // By returning true, the text entry signals that it want to receive the signal for this event.
02414           // Otherwise, the parent view of the text entry would be looking for another view to receive keynav focus to.
02415           return true;
02416         }
02417 
02418         return false;
02419       }
02420     }
02421 
02422     if ((eventType == NUX_KEYDOWN) && (key_nav_mode_ == true) && (text_input_mode_ == true) && (ime_active_ == false))
02423     {
02424       // Enable to exit the TextEntry when in write mode(hack for unity dash)
02425       if (key_sym == NUX_VK_UP ||
02426       key_sym == NUX_VK_DOWN ||
02427       key_sym == NUX_VK_ESCAPE)
02428       {
02429         if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_up_) && NUX_VK_UP)
02430         {
02431           // By returning true, the text entry signals that it want to receive the signal for this event.
02432           // Otherwise, the parent view of the text entry would be looking for another view to receive keynav focus to.
02433           return true;
02434         }
02435 
02436         if ((!multiline_) && (!lose_key_focus_on_key_nav_direction_down_) && NUX_VK_DOWN)
02437         {
02438           // By returning true, the text entry signals that it want to receive the signal for this event.
02439           // Otherwise, the parent view of the text entry would be looking for another view to receive keynav focus to.
02440           return true;
02441         }
02442 
02443         return false;
02444       }
02445     }
02446 
02447     if ((eventType == NUX_KEYDOWN) && (key_nav_mode_ == false) && (text_input_mode_ == false))
02448     {
02449       return false;
02450     }
02451 
02452     return true;
02453   }
02454 
02456 
02457   void TextEntry::MoveCursorToLineStart()
02458   {
02459     MoveCursor(DISPLAY_LINE_ENDS, -1, 0);
02460   }
02461 
02462   void TextEntry::MoveCursorToLineEnd()
02463   {
02464     MoveCursor(DISPLAY_LINE_ENDS, 1, 0);
02465   }
02466 
02467   void TextEntry::SetLoseKeyFocusOnKeyNavDirectionUp(bool b)
02468   {
02469     lose_key_focus_on_key_nav_direction_up_ = b;
02470   }
02471 
02472   bool TextEntry::GetLoseKeyFocusOnKeyNavDirectionUp() const
02473   {
02474     return lose_key_focus_on_key_nav_direction_up_;
02475   }
02476 
02477   void TextEntry::SetLoseKeyFocusOnKeyNavDirectionDown(bool b)
02478   {
02479     lose_key_focus_on_key_nav_direction_down_ = b;
02480   }
02481 
02482   bool TextEntry::GetLoseKeyFocusOnKeyNavDirectionDown() const
02483   {
02484     return lose_key_focus_on_key_nav_direction_down_;
02485   }
02486 
02487   bool TextEntry::IsInTextInputMode() const
02488   {
02489     return text_input_mode_;
02490   }
02491 
02492 }