1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 #include "ppapi/c/dev/ppb_cursor_control_dev.h"
10 #include "ppapi/c/ppb_console.h"
11 #include "ppapi/cpp/completion_callback.h"
12 #include "ppapi/cpp/dev/font_dev.h"
13 #include "ppapi/cpp/dev/ime_input_event_dev.h"
14 #include "ppapi/cpp/dev/text_input_dev.h"
15 #include "ppapi/cpp/graphics_2d.h"
16 #include "ppapi/cpp/image_data.h"
17 #include "ppapi/cpp/input_event.h"
18 #include "ppapi/cpp/instance.h"
19 #include "ppapi/cpp/module.h"
20 #include "ppapi/cpp/rect.h"
21 #include "ppapi/cpp/size.h"
25 // Extracted from: ui/base/keycodes/keyboard_codes.h
36 const uint32_t kTextfieldBgColor
= 0xffffffff;
37 const uint32_t kTextfieldTextColor
= 0xff000000;
38 const uint32_t kTextfieldCaretColor
= 0xff000000;
39 const uint32_t kTextfieldPreeditTextColor
= 0xffff0000;
40 const uint32_t kTextfieldSelectionBackgroundColor
= 0xffeecccc;
41 const uint32_t kTextfieldUnderlineColorMain
= 0xffff0000;
42 const uint32_t kTextfieldUnderlineColorSub
= 0xffddaaaa;
44 void FillRect(pp::ImageData
* image
,
45 int left
, int top
, int width
, int height
,
47 for (int y
= std::max(0, top
);
48 y
< std::min(image
->size().height() - 1, top
+ height
);
50 for (int x
= std::max(0, left
);
51 x
< std::min(image
->size().width() - 1, left
+ width
);
53 *image
->GetAddr32(pp::Point(x
, y
)) = color
;
57 void FillRect(pp::ImageData
* image
, const pp::Rect
& rect
, uint32_t color
) {
58 FillRect(image
, rect
.x(), rect
.y(), rect
.width(), rect
.height(), color
);
61 size_t GetPrevCharOffsetUtf8(const std::string
& str
, size_t current_pos
) {
62 size_t i
= current_pos
;
66 while (i
> 0 && (str
[i
] & 0xc0) == 0x80);
71 size_t GetNextCharOffsetUtf8(const std::string
& str
, size_t current_pos
) {
72 size_t i
= current_pos
;
76 while (i
< str
.size() && (str
[i
] & 0xc0) == 0x80);
81 size_t GetNthCharOffsetUtf8(const std::string
& str
, size_t n
) {
83 for (size_t step
= 0; step
< n
; ++step
)
84 i
= GetNextCharOffsetUtf8(str
, i
);
90 class TextFieldStatusHandler
{
92 virtual ~TextFieldStatusHandler() {}
93 virtual void FocusIn(const pp::Rect
& caret
, const pp::Rect
& bounding_box
) {}
94 virtual void FocusOut() {}
95 virtual void UpdateSelection(const std::string
& text
) {}
98 class TextFieldStatusNotifyingHandler
: public TextFieldStatusHandler
{
100 explicit TextFieldStatusNotifyingHandler(pp::Instance
* instance
)
101 : textinput_control_(instance
) {
105 // Implement TextFieldStatusHandler.
106 virtual void FocusIn(const pp::Rect
& caret
, const pp::Rect
& bounding_box
) {
107 textinput_control_
.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT
);
108 textinput_control_
.UpdateCaretPosition(caret
, bounding_box
);
110 virtual void FocusOut() {
111 textinput_control_
.CancelCompositionText();
112 textinput_control_
.SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
114 virtual void UpdateSelection(const std::string
& text
) {
115 textinput_control_
.SetSelectionText(text
);
116 textinput_control_
.SelectionChanged();
120 class MyTextInput
: public pp::TextInput_Dev
{
122 MyTextInput(pp::Instance
* instance
) : pp::TextInput_Dev(instance
) {}
123 virtual void RequestSurroundingText(uint32_t characters
) {
124 UpdateSurroundingText(selection_text_
, 0, selection_text_
.size());
126 void SetSelectionText(const std::string
& text
) { selection_text_
= text
; }
127 std::string selection_text_
;
129 MyTextInput textinput_control_
;
132 // Hand-made text field for demonstrating text input API.
135 MyTextField(pp::Instance
* instance
, TextFieldStatusHandler
* handler
,
136 int x
, int y
, int width
, int height
)
137 : instance_(instance
),
138 status_handler_(handler
),
139 area_(x
, y
, width
, height
),
140 font_size_(height
- 2),
141 caret_pos_(std::string::npos
),
142 anchor_pos_(std::string::npos
) {
143 pp::FontDescription_Dev desc
;
144 desc
.set_family(PP_FONTFAMILY_SANSSERIF
);
145 desc
.set_size(font_size_
);
146 font_
= pp::Font_Dev(instance_
, desc
);
149 // Paint on the specified ImageData.
150 void PaintOn(pp::ImageData
* image
, pp::Rect clip
) {
151 clip
= clip
.Intersect(area_
);
152 FillRect(image
, clip
, kTextfieldBgColor
);
154 if (caret_pos_
!= std::string::npos
) {
155 int offset
= area_
.x();
156 // selection (for the case without composition text)
157 if (composition_
.empty() && HasSelection()) {
158 int left_x
= font_
.MeasureSimpleText(
159 utf8_text_
.substr(0, SelectionLeft()));
160 int right_x
= font_
.MeasureSimpleText(
161 utf8_text_
.substr(0, SelectionRight()));
162 FillRect(image
, offset
+ left_x
, area_
.y(), right_x
- left_x
,
163 area_
.height(), kTextfieldSelectionBackgroundColor
);
167 std::string str
= utf8_text_
.substr(0, caret_pos_
);
170 pp::TextRun_Dev(str
.c_str(), false, false),
171 pp::Point(offset
, area_
.y() + font_size_
),
175 offset
+= font_
.MeasureSimpleText(str
);
179 const std::string
& str
= composition_
;
181 if (composition_selection_
.first
!= composition_selection_
.second
) {
182 int left_x
= font_
.MeasureSimpleText(
183 str
.substr(0, composition_selection_
.first
));
184 int right_x
= font_
.MeasureSimpleText(
185 str
.substr(0, composition_selection_
.second
));
186 FillRect(image
, offset
+ left_x
, area_
.y(), right_x
- left_x
,
187 area_
.height(), kTextfieldSelectionBackgroundColor
);
192 pp::TextRun_Dev(str
.c_str(), false, false),
193 pp::Point(offset
, area_
.y() + font_size_
),
194 kTextfieldPreeditTextColor
,
197 for (size_t i
= 0; i
< segments_
.size(); ++i
) {
198 size_t l
= segments_
[i
].first
;
199 size_t r
= segments_
[i
].second
;
201 int lx
= font_
.MeasureSimpleText(str
.substr(0, l
));
202 int rx
= font_
.MeasureSimpleText(str
.substr(0, r
));
204 offset
+ lx
+ 2, area_
.y() + font_size_
+ 1,
206 i
== static_cast<size_t>(target_segment_
) ?
207 kTextfieldUnderlineColorMain
:
208 kTextfieldUnderlineColorSub
);
212 int caretx
= font_
.MeasureSimpleText(
213 str
.substr(0, composition_selection_
.first
));
215 pp::Rect(offset
+ caretx
, area_
.y(), 2, area_
.height()),
216 kTextfieldCaretColor
);
217 offset
+= font_
.MeasureSimpleText(str
);
221 std::string str
= utf8_text_
.substr(caret_pos_
);
224 pp::TextRun_Dev(str
.c_str(), false, false),
225 pp::Point(offset
, area_
.y() + font_size_
),
233 pp::TextRun_Dev(utf8_text_
.c_str(), false, false),
234 pp::Point(area_
.x(), area_
.y() + font_size_
),
241 // Update current composition text.
243 const std::string
& text
,
244 const std::vector
< std::pair
<uint32_t, uint32_t> >& segments
,
245 int32_t target_segment
,
246 const std::pair
<uint32_t, uint32_t>& selection
) {
247 if (HasSelection() && !text
.empty())
248 InsertText(std::string());
250 segments_
= segments
;
251 target_segment_
= target_segment
;
252 composition_selection_
= selection
;
256 // Is the text field focused?
257 bool Focused() const {
258 return caret_pos_
!= std::string::npos
;
261 // Does the coordinate (x,y) is contained inside the edit box?
262 bool Contains(int x
, int y
) const {
263 return area_
.Contains(x
, y
);
266 // Resets the content text.
267 void SetText(const std::string
& text
) {
270 caret_pos_
= anchor_pos_
= text
.size();
275 // Inserts a text at the current caret position.
276 void InsertText(const std::string
& text
) {
279 utf8_text_
.replace(SelectionLeft(), SelectionRight() - SelectionLeft(),
281 caret_pos_
= anchor_pos_
= SelectionLeft() + text
.size();
285 // Handles mouse click event and changes the focus state.
286 bool RefocusByMouseClick(int x
, int y
) {
287 if (!Contains(x
, y
)) {
288 // The text field is unfocused.
289 caret_pos_
= anchor_pos_
= std::string::npos
;
293 // The text field is focused.
294 size_t n
= font_
.CharacterOffsetForPixel(
295 pp::TextRun_Dev(utf8_text_
.c_str()), x
- area_
.x());
296 caret_pos_
= anchor_pos_
= GetNthCharOffsetUtf8(utf8_text_
, n
);
301 void MouseDrag(int x
, int y
) {
304 size_t n
= font_
.CharacterOffsetForPixel(
305 pp::TextRun_Dev(utf8_text_
.c_str()), x
- area_
.x());
306 caret_pos_
= GetNthCharOffsetUtf8(utf8_text_
, n
);
309 void MouseUp(int x
, int y
) {
315 void KeyLeft(bool shift
) {
318 // Move caret to the head of the selection or to the previous character.
319 if (!shift
&& HasSelection())
320 caret_pos_
= SelectionLeft();
322 caret_pos_
= GetPrevCharOffsetUtf8(utf8_text_
, caret_pos_
);
323 // Move the anchor if the shift key is not pressed.
325 anchor_pos_
= caret_pos_
;
329 void KeyRight(bool shift
) {
332 // Move caret to the end of the selection or to the next character.
333 if (!shift
&& HasSelection())
334 caret_pos_
= SelectionRight();
336 caret_pos_
= GetNextCharOffsetUtf8(utf8_text_
, caret_pos_
);
337 // Move the anchor if the shift key is not pressed.
339 anchor_pos_
= caret_pos_
;
346 if (HasSelection()) {
347 InsertText(std::string());
349 size_t i
= GetNextCharOffsetUtf8(utf8_text_
, caret_pos_
);
350 utf8_text_
.erase(caret_pos_
, i
- caret_pos_
);
355 void KeyBackspace() {
358 if (HasSelection()) {
359 InsertText(std::string());
360 } else if (caret_pos_
!= 0) {
361 size_t i
= GetPrevCharOffsetUtf8(utf8_text_
, caret_pos_
);
362 utf8_text_
.erase(i
, caret_pos_
- i
);
363 caret_pos_
= anchor_pos_
= i
;
369 // Notify the plugin instance that the caret position has changed.
370 void CaretPosChanged() {
372 std::string str
= utf8_text_
.substr(0, caret_pos_
);
373 if (!composition_
.empty())
374 str
+= composition_
.substr(0, composition_selection_
.first
);
375 int px
= font_
.MeasureSimpleText(str
);
376 pp::Rect
caret(area_
.x() + px
, area_
.y(), 0, area_
.height() + 2);
377 status_handler_
->FocusIn(caret
, area_
);
378 status_handler_
->UpdateSelection(
379 utf8_text_
.substr(SelectionLeft(),
380 SelectionRight() - SelectionLeft()));
383 size_t SelectionLeft() const {
384 return std::min(caret_pos_
, anchor_pos_
);
386 size_t SelectionRight() const {
387 return std::max(caret_pos_
, anchor_pos_
);
389 bool HasSelection() const {
390 return caret_pos_
!= anchor_pos_
;
393 pp::Instance
* instance_
;
394 TextFieldStatusHandler
* status_handler_
;
399 std::string utf8_text_
;
402 std::string composition_
;
403 std::vector
< std::pair
<uint32_t, uint32_t> > segments_
;
404 std::pair
<uint32_t, uint32_t> composition_selection_
;
408 class MyInstance
: public pp::Instance
{
410 explicit MyInstance(PP_Instance instance
)
411 : pp::Instance(instance
),
412 status_handler_(new TextFieldStatusHandler
),
417 delete status_handler_
;
420 virtual bool Init(uint32_t argc
, const char* argn
[], const char* argv
[]) {
421 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
);
422 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD
);
424 for (uint32_t i
= 0; i
< argc
; ++i
) {
425 if (argn
[i
] == std::string("ime")) {
426 if (argv
[i
] == std::string("no")) {
427 // Example of NO-IME plugins (e.g., games).
429 // When a plugin never wants to accept text input, at initialization
430 // explicitly turn off the text input feature by calling:
431 pp::TextInput_Dev(this).SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
432 } else if (argv
[i
] == std::string("unaware")) {
433 // Demonstrating the behavior of IME-unaware plugins.
434 // Never call any text input related APIs.
436 // In such a case, the plugin is assumed to always accept text input.
437 // For example, when the plugin is focused in touch devices a virtual
438 // keyboard may pop up, or in environment IME is used, users can type
439 // text via IME on the plugin. The characters are delivered to the
440 // plugin via PP_INPUTEVENT_TYPE_CHAR events.
441 } else if (argv
[i
] == std::string("caretmove")) {
442 // Demonstrating the behavior of plugins with limited IME support.
444 // It uses SetTextInputType() and UpdateCaretPosition() API to notify
445 // text input status to the browser, but unable to handle inline
446 // compositions. By using the notified information. the browser can,
447 // say, show virtual keyboards or IMEs only at appropriate timing
448 // that the plugin does need to accept text input.
449 delete status_handler_
;
450 status_handler_
= new TextFieldStatusNotifyingHandler(this);
451 } else if (argv
[i
] == std::string("full")) {
452 // Demonstrating the behavior of plugins fully supporting IME.
454 // It notifies updates of caret positions to the browser,
455 // and handles all text input events by itself.
456 delete status_handler_
;
457 status_handler_
= new TextFieldStatusNotifyingHandler(this);
458 RequestInputEvents(PP_INPUTEVENT_CLASS_IME
);
464 textfield_
.push_back(MyTextField(this, status_handler_
,
466 textfield_
.back().SetText("Hello");
467 textfield_
.push_back(MyTextField(this, status_handler_
,
469 textfield_
.back().SetText("World");
474 virtual bool HandleInputEvent(const pp::InputEvent
& event
) {
476 switch (event
.GetType()) {
477 case PP_INPUTEVENT_TYPE_MOUSEDOWN
: {
478 const pp::MouseInputEvent
mouseEvent(event
);
479 ret
= OnMouseDown(mouseEvent
);
482 case PP_INPUTEVENT_TYPE_MOUSEMOVE
: {
483 const pp::MouseInputEvent
mouseEvent(event
);
484 ret
= OnMouseMove(mouseEvent
);
487 case PP_INPUTEVENT_TYPE_MOUSEUP
: {
488 const pp::MouseInputEvent
mouseEvent(event
);
489 ret
= OnMouseUp(mouseEvent
);
492 case PP_INPUTEVENT_TYPE_KEYDOWN
: {
494 const pp::KeyboardInputEvent
keyEvent(event
);
495 ret
= OnKeyDown(keyEvent
);
498 case PP_INPUTEVENT_TYPE_CHAR
: {
499 const pp::KeyboardInputEvent
keyEvent(event
);
500 Log("Char [" + keyEvent
.GetCharacterText().AsString() + "]");
501 ret
= OnChar(keyEvent
);
504 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START
: {
505 const pp::IMEInputEvent_Dev
imeEvent(event
);
506 Log("CompositionStart [" + imeEvent
.GetText().AsString() + "]");
510 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE
: {
511 const pp::IMEInputEvent_Dev
imeEvent(event
);
512 Log("CompositionUpdate [" + imeEvent
.GetText().AsString() + "]");
513 ret
= OnCompositionUpdate(imeEvent
);
516 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END
: {
517 const pp::IMEInputEvent_Dev
imeEvent(event
);
518 Log("CompositionEnd [" + imeEvent
.GetText().AsString() + "]");
519 ret
= OnCompositionEnd(imeEvent
);
522 case PP_INPUTEVENT_TYPE_IME_TEXT
: {
523 const pp::IMEInputEvent_Dev
imeEvent(event
);
524 Log("ImeText [" + imeEvent
.GetText().AsString() + "]");
525 ret
= OnImeText(imeEvent
);
531 if (ret
&& (dragging_
|| event
.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE
))
536 virtual void DidChangeView(const pp::Rect
& position
, const pp::Rect
& clip
) {
537 if (position
.size() == last_size_
)
539 last_size_
= position
.size();
544 bool OnCompositionUpdate(const pp::IMEInputEvent_Dev
& ev
) {
545 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
546 it
!= textfield_
.end();
549 std::vector
< std::pair
<uint32_t, uint32_t> > segs
;
550 for (uint32_t i
= 0; i
< ev
.GetSegmentNumber(); ++i
)
551 segs
.push_back(std::make_pair(ev
.GetSegmentOffset(i
),
552 ev
.GetSegmentOffset(i
+ 1)));
553 it
->SetComposition(ev
.GetText().AsString(),
555 ev
.GetTargetSegment(),
563 bool OnCompositionEnd(const pp::IMEInputEvent_Dev
& ev
) {
564 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
565 it
!= textfield_
.end();
568 it
->SetComposition(std::string(),
569 std::vector
<std::pair
<uint32_t, uint32_t> >(),
571 std::make_pair(0, 0));
578 bool OnMouseDown(const pp::MouseInputEvent
& ev
) {
581 bool anyone_focused
= false;
582 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
583 it
!= textfield_
.end();
585 if (it
->RefocusByMouseClick(ev
.GetPosition().x(),
586 ev
.GetPosition().y())) {
587 anyone_focused
= true;
591 status_handler_
->FocusOut();
595 bool OnMouseMove(const pp::MouseInputEvent
& ev
) {
596 const PPB_CursorControl_Dev
* cursor_control
=
597 reinterpret_cast<const PPB_CursorControl_Dev
*>(
598 pp::Module::Get()->GetBrowserInterface(
599 PPB_CURSOR_CONTROL_DEV_INTERFACE
));
603 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
604 it
!= textfield_
.end();
606 if (it
->Contains(ev
.GetPosition().x(),
607 ev
.GetPosition().y())) {
608 cursor_control
->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM
,
610 if (it
->Focused() && dragging_
)
611 it
->MouseDrag(ev
.GetPosition().x(), ev
.GetPosition().y());
615 cursor_control
->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER
,
620 bool OnMouseUp(const pp::MouseInputEvent
& ev
) {
622 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
623 it
!= textfield_
.end();
626 it
->MouseUp(ev
.GetPosition().x(), ev
.GetPosition().y());
630 bool OnKeyDown(const pp::KeyboardInputEvent
& ev
) {
631 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
632 it
!= textfield_
.end();
635 bool shift
= ev
.GetModifiers() & PP_INPUTEVENT_MODIFIER_SHIFTKEY
;
636 switch (ev
.GetKeyCode()) {
656 bool OnChar(const pp::KeyboardInputEvent
& ev
) {
657 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
658 it
!= textfield_
.end();
661 std::string str
= ev
.GetCharacterText().AsString();
662 if (str
!= "\r" && str
!= "\n")
670 bool OnImeText(const pp::IMEInputEvent_Dev ev
) {
671 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
672 it
!= textfield_
.end();
675 it
->InsertText(ev
.GetText().AsString());
683 pp::Rect
clip(0, 0, last_size_
.width(), last_size_
.height());
687 void PaintClip(const pp::Rect
& clip
) {
688 pp::ImageData
image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL
, last_size_
, true);
689 pp::Graphics2D
device(this, last_size_
, false);
690 BindGraphics(device
);
692 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
693 it
!= textfield_
.end();
695 it
->PaintOn(&image
, clip
);
698 device
.PaintImageData(image
, pp::Point(0, 0));
699 device
.Flush(pp::CompletionCallback(&OnFlush
, this));
702 static void OnFlush(void* user_data
, int32_t result
) {}
704 // Prints a debug message.
705 void Log(const pp::Var
& value
) {
706 const PPB_Console
* console
= reinterpret_cast<const PPB_Console
*>(
707 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE
));
710 console
->Log(pp_instance(), PP_LOGLEVEL_LOG
, value
.pp_var());
713 // IME Control interface.
714 TextFieldStatusHandler
* status_handler_
;
716 // Remembers the size of this instance.
719 // Holds instances of text fields.
720 std::vector
<MyTextField
> textfield_
;
722 // Whether or not during a drag operation.
726 class MyModule
: public pp::Module
{
727 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
728 return new MyInstance(instance
);
734 Module
* CreateModule() {
735 return new MyModule();