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/graphics_2d.h"
14 #include "ppapi/cpp/image_data.h"
15 #include "ppapi/cpp/input_event.h"
16 #include "ppapi/cpp/instance.h"
17 #include "ppapi/cpp/module.h"
18 #include "ppapi/cpp/rect.h"
19 #include "ppapi/cpp/size.h"
20 #include "ppapi/cpp/text_input_controller.h"
24 // Extracted from: ui/base/keycodes/keyboard_codes.h
35 const uint32_t kTextfieldBgColor
= 0xffffffff;
36 const uint32_t kTextfieldTextColor
= 0xff000000;
37 const uint32_t kTextfieldCaretColor
= 0xff000000;
38 const uint32_t kTextfieldPreeditTextColor
= 0xffff0000;
39 const uint32_t kTextfieldSelectionBackgroundColor
= 0xffeecccc;
40 const uint32_t kTextfieldUnderlineColorMain
= 0xffff0000;
41 const uint32_t kTextfieldUnderlineColorSub
= 0xffddaaaa;
43 void FillRect(pp::ImageData
* image
,
44 int left
, int top
, int width
, int height
,
46 for (int y
= std::max(0, top
);
47 y
< std::min(image
->size().height() - 1, top
+ height
);
49 for (int x
= std::max(0, left
);
50 x
< std::min(image
->size().width() - 1, left
+ width
);
52 *image
->GetAddr32(pp::Point(x
, y
)) = color
;
56 void FillRect(pp::ImageData
* image
, const pp::Rect
& rect
, uint32_t color
) {
57 FillRect(image
, rect
.x(), rect
.y(), rect
.width(), rect
.height(), color
);
60 size_t GetPrevCharOffsetUtf8(const std::string
& str
, size_t current_pos
) {
61 size_t i
= current_pos
;
65 while (i
> 0 && (str
[i
] & 0xc0) == 0x80);
70 size_t GetNextCharOffsetUtf8(const std::string
& str
, size_t current_pos
) {
71 size_t i
= current_pos
;
75 while (i
< str
.size() && (str
[i
] & 0xc0) == 0x80);
80 size_t GetNthCharOffsetUtf8(const std::string
& str
, size_t n
) {
82 for (size_t step
= 0; step
< n
; ++step
)
83 i
= GetNextCharOffsetUtf8(str
, i
);
89 class TextFieldStatusHandler
{
91 virtual ~TextFieldStatusHandler() {}
92 virtual void FocusIn(const pp::Rect
& caret
) {}
93 virtual void FocusOut() {}
94 virtual void UpdateSelection(const std::string
& text
) {}
97 class TextFieldStatusNotifyingHandler
: public TextFieldStatusHandler
{
99 explicit TextFieldStatusNotifyingHandler(pp::Instance
* instance
)
100 : textinput_control_(instance
) {
104 // Implement TextFieldStatusHandler.
105 virtual void FocusIn(const pp::Rect
& caret
) {
106 textinput_control_
.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT
);
107 textinput_control_
.UpdateCaretPosition(caret
);
109 virtual void FocusOut() {
110 textinput_control_
.CancelCompositionText();
111 textinput_control_
.SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
113 virtual void UpdateSelection(const std::string
& text
) {
114 textinput_control_
.UpdateSurroundingText(text
, 0, text
.size());
118 pp::TextInputController textinput_control_
;
121 // Hand-made text field for demonstrating text input API.
124 MyTextField(pp::Instance
* instance
, TextFieldStatusHandler
* handler
,
125 int x
, int y
, int width
, int height
)
126 : instance_(instance
),
127 status_handler_(handler
),
128 area_(x
, y
, width
, height
),
129 font_size_(height
- 2),
130 caret_pos_(std::string::npos
),
131 anchor_pos_(std::string::npos
),
133 pp::FontDescription_Dev desc
;
134 desc
.set_family(PP_FONTFAMILY_SANSSERIF
);
135 desc
.set_size(font_size_
);
136 font_
= pp::Font_Dev(instance_
, desc
);
139 // Paint on the specified ImageData.
140 void PaintOn(pp::ImageData
* image
, pp::Rect clip
) {
141 clip
= clip
.Intersect(area_
);
142 FillRect(image
, clip
, kTextfieldBgColor
);
144 if (caret_pos_
!= std::string::npos
) {
145 int offset
= area_
.x();
146 // selection (for the case without composition text)
147 if (composition_
.empty() && HasSelection()) {
148 int left_x
= font_
.MeasureSimpleText(
149 utf8_text_
.substr(0, SelectionLeft()));
150 int right_x
= font_
.MeasureSimpleText(
151 utf8_text_
.substr(0, SelectionRight()));
152 FillRect(image
, offset
+ left_x
, area_
.y(), right_x
- left_x
,
153 area_
.height(), kTextfieldSelectionBackgroundColor
);
157 std::string str
= utf8_text_
.substr(0, caret_pos_
);
160 pp::TextRun_Dev(str
.c_str(), false, false),
161 pp::Point(offset
, area_
.y() + font_size_
),
165 offset
+= font_
.MeasureSimpleText(str
);
169 const std::string
& str
= composition_
;
171 if (composition_selection_
.first
!= composition_selection_
.second
) {
172 int left_x
= font_
.MeasureSimpleText(
173 str
.substr(0, composition_selection_
.first
));
174 int right_x
= font_
.MeasureSimpleText(
175 str
.substr(0, composition_selection_
.second
));
176 FillRect(image
, offset
+ left_x
, area_
.y(), right_x
- left_x
,
177 area_
.height(), kTextfieldSelectionBackgroundColor
);
182 pp::TextRun_Dev(str
.c_str(), false, false),
183 pp::Point(offset
, area_
.y() + font_size_
),
184 kTextfieldPreeditTextColor
,
187 for (size_t i
= 0; i
< segments_
.size(); ++i
) {
188 size_t l
= segments_
[i
].first
;
189 size_t r
= segments_
[i
].second
;
191 int lx
= font_
.MeasureSimpleText(str
.substr(0, l
));
192 int rx
= font_
.MeasureSimpleText(str
.substr(0, r
));
194 offset
+ lx
+ 2, area_
.y() + font_size_
+ 1,
196 i
== static_cast<size_t>(target_segment_
) ?
197 kTextfieldUnderlineColorMain
:
198 kTextfieldUnderlineColorSub
);
202 int caretx
= font_
.MeasureSimpleText(
203 str
.substr(0, composition_selection_
.first
));
205 pp::Rect(offset
+ caretx
, area_
.y(), 2, area_
.height()),
206 kTextfieldCaretColor
);
207 offset
+= font_
.MeasureSimpleText(str
);
211 std::string str
= utf8_text_
.substr(caret_pos_
);
214 pp::TextRun_Dev(str
.c_str(), false, false),
215 pp::Point(offset
, area_
.y() + font_size_
),
223 pp::TextRun_Dev(utf8_text_
.c_str(), false, false),
224 pp::Point(area_
.x(), area_
.y() + font_size_
),
231 // Update current composition text.
233 const std::string
& text
,
234 const std::vector
< std::pair
<uint32_t, uint32_t> >& segments
,
235 int32_t target_segment
,
236 const std::pair
<uint32_t, uint32_t>& selection
) {
237 if (HasSelection() && !text
.empty())
238 InsertText(std::string());
240 segments_
= segments
;
241 target_segment_
= target_segment
;
242 composition_selection_
= selection
;
246 // Is the text field focused?
247 bool Focused() const {
248 return caret_pos_
!= std::string::npos
;
251 // Does the coordinate (x,y) is contained inside the edit box?
252 bool Contains(int x
, int y
) const {
253 return area_
.Contains(x
, y
);
256 // Resets the content text.
257 void SetText(const std::string
& text
) {
260 caret_pos_
= anchor_pos_
= text
.size();
265 // Inserts a text at the current caret position.
266 void InsertText(const std::string
& text
) {
269 utf8_text_
.replace(SelectionLeft(), SelectionRight() - SelectionLeft(),
271 caret_pos_
= anchor_pos_
= SelectionLeft() + text
.size();
275 // Handles mouse click event and changes the focus state.
276 bool RefocusByMouseClick(int x
, int y
) {
277 if (!Contains(x
, y
)) {
278 // The text field is unfocused.
279 caret_pos_
= anchor_pos_
= std::string::npos
;
283 // The text field is focused.
284 size_t n
= font_
.CharacterOffsetForPixel(
285 pp::TextRun_Dev(utf8_text_
.c_str()), x
- area_
.x());
286 caret_pos_
= anchor_pos_
= GetNthCharOffsetUtf8(utf8_text_
, n
);
291 void MouseDrag(int x
, int y
) {
294 size_t n
= font_
.CharacterOffsetForPixel(
295 pp::TextRun_Dev(utf8_text_
.c_str()), x
- area_
.x());
296 caret_pos_
= GetNthCharOffsetUtf8(utf8_text_
, n
);
299 void MouseUp(int x
, int y
) {
305 void KeyLeft(bool shift
) {
308 // Move caret to the head of the selection or to the previous character.
309 if (!shift
&& HasSelection())
310 caret_pos_
= SelectionLeft();
312 caret_pos_
= GetPrevCharOffsetUtf8(utf8_text_
, caret_pos_
);
313 // Move the anchor if the shift key is not pressed.
315 anchor_pos_
= caret_pos_
;
319 void KeyRight(bool shift
) {
322 // Move caret to the end of the selection or to the next character.
323 if (!shift
&& HasSelection())
324 caret_pos_
= SelectionRight();
326 caret_pos_
= GetNextCharOffsetUtf8(utf8_text_
, caret_pos_
);
327 // Move the anchor if the shift key is not pressed.
329 anchor_pos_
= caret_pos_
;
336 if (HasSelection()) {
337 InsertText(std::string());
339 size_t i
= GetNextCharOffsetUtf8(utf8_text_
, caret_pos_
);
340 utf8_text_
.erase(caret_pos_
, i
- caret_pos_
);
345 void KeyBackspace() {
348 if (HasSelection()) {
349 InsertText(std::string());
350 } else if (caret_pos_
!= 0) {
351 size_t i
= GetPrevCharOffsetUtf8(utf8_text_
, caret_pos_
);
352 utf8_text_
.erase(i
, caret_pos_
- i
);
353 caret_pos_
= anchor_pos_
= i
;
359 // Notify the plugin instance that the caret position has changed.
360 void CaretPosChanged() {
362 std::string str
= utf8_text_
.substr(0, caret_pos_
);
363 if (!composition_
.empty())
364 str
+= composition_
.substr(0, composition_selection_
.first
);
365 int px
= font_
.MeasureSimpleText(str
);
366 pp::Rect
caret(area_
.x() + px
, area_
.y(), 0, area_
.height() + 2);
367 status_handler_
->FocusIn(caret
);
368 status_handler_
->UpdateSelection(
369 utf8_text_
.substr(SelectionLeft(),
370 SelectionRight() - SelectionLeft()));
373 size_t SelectionLeft() const {
374 return std::min(caret_pos_
, anchor_pos_
);
376 size_t SelectionRight() const {
377 return std::max(caret_pos_
, anchor_pos_
);
379 bool HasSelection() const {
380 return caret_pos_
!= anchor_pos_
;
383 pp::Instance
* instance_
;
384 TextFieldStatusHandler
* status_handler_
;
389 std::string utf8_text_
;
392 std::string composition_
;
393 std::vector
< std::pair
<uint32_t, uint32_t> > segments_
;
394 std::pair
<uint32_t, uint32_t> composition_selection_
;
398 class MyInstance
: public pp::Instance
{
400 explicit MyInstance(PP_Instance instance
)
401 : pp::Instance(instance
),
402 status_handler_(new TextFieldStatusHandler
),
407 delete status_handler_
;
410 virtual bool Init(uint32_t argc
, const char* argn
[], const char* argv
[]) {
411 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
);
412 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD
);
414 for (uint32_t i
= 0; i
< argc
; ++i
) {
415 if (argn
[i
] == std::string("ime")) {
416 if (argv
[i
] == std::string("no")) {
417 // Example of NO-IME plugins (e.g., games).
419 // When a plugin never wants to accept text input, at initialization
420 // explicitly turn off the text input feature by calling:
421 pp::TextInputController(this).SetTextInputType(
422 PP_TEXTINPUT_TYPE_NONE
);
423 } else if (argv
[i
] == std::string("unaware")) {
424 // Demonstrating the behavior of IME-unaware plugins.
425 // Never call any text input related APIs.
427 // In such a case, the plugin is assumed to always accept text input.
428 // For example, when the plugin is focused in touch devices a virtual
429 // keyboard may pop up, or in environment IME is used, users can type
430 // text via IME on the plugin. The characters are delivered to the
431 // plugin via PP_INPUTEVENT_TYPE_CHAR events.
432 } else if (argv
[i
] == std::string("caretmove")) {
433 // Demonstrating the behavior of plugins with limited IME support.
435 // It uses SetTextInputType() and UpdateCaretPosition() API to notify
436 // text input status to the browser, but unable to handle inline
437 // compositions. By using the notified information. the browser can,
438 // say, show virtual keyboards or IMEs only at appropriate timing
439 // that the plugin does need to accept text input.
440 delete status_handler_
;
441 status_handler_
= new TextFieldStatusNotifyingHandler(this);
442 } else if (argv
[i
] == std::string("full")) {
443 // Demonstrating the behavior of plugins fully supporting IME.
445 // It notifies updates of caret positions to the browser,
446 // and handles all text input events by itself.
447 delete status_handler_
;
448 status_handler_
= new TextFieldStatusNotifyingHandler(this);
449 RequestInputEvents(PP_INPUTEVENT_CLASS_IME
);
455 textfield_
.push_back(MyTextField(this, status_handler_
,
457 textfield_
.back().SetText("Hello");
458 textfield_
.push_back(MyTextField(this, status_handler_
,
460 textfield_
.back().SetText("World");
465 virtual bool HandleInputEvent(const pp::InputEvent
& event
) {
467 switch (event
.GetType()) {
468 case PP_INPUTEVENT_TYPE_MOUSEDOWN
: {
469 const pp::MouseInputEvent
mouseEvent(event
);
470 ret
= OnMouseDown(mouseEvent
);
473 case PP_INPUTEVENT_TYPE_MOUSEMOVE
: {
474 const pp::MouseInputEvent
mouseEvent(event
);
475 ret
= OnMouseMove(mouseEvent
);
478 case PP_INPUTEVENT_TYPE_MOUSEUP
: {
479 const pp::MouseInputEvent
mouseEvent(event
);
480 ret
= OnMouseUp(mouseEvent
);
483 case PP_INPUTEVENT_TYPE_KEYDOWN
: {
485 const pp::KeyboardInputEvent
keyEvent(event
);
486 ret
= OnKeyDown(keyEvent
);
489 case PP_INPUTEVENT_TYPE_CHAR
: {
490 const pp::KeyboardInputEvent
keyEvent(event
);
491 Log("Char [" + keyEvent
.GetCharacterText().AsString() + "]");
492 ret
= OnChar(keyEvent
);
495 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START
: {
496 const pp::IMEInputEvent
imeEvent(event
);
497 Log("CompositionStart [" + imeEvent
.GetText().AsString() + "]");
501 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE
: {
502 const pp::IMEInputEvent
imeEvent(event
);
503 Log("CompositionUpdate [" + imeEvent
.GetText().AsString() + "]");
504 ret
= OnCompositionUpdate(imeEvent
);
507 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END
: {
508 const pp::IMEInputEvent
imeEvent(event
);
509 Log("CompositionEnd [" + imeEvent
.GetText().AsString() + "]");
510 ret
= OnCompositionEnd(imeEvent
);
513 case PP_INPUTEVENT_TYPE_IME_TEXT
: {
514 const pp::IMEInputEvent
imeEvent(event
);
515 Log("ImeText [" + imeEvent
.GetText().AsString() + "]");
516 ret
= OnImeText(imeEvent
);
522 if (ret
&& (dragging_
|| event
.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE
))
527 virtual void DidChangeView(const pp::Rect
& position
, const pp::Rect
& clip
) {
528 if (position
.size() == last_size_
)
530 last_size_
= position
.size();
535 bool OnCompositionUpdate(const pp::IMEInputEvent
& ev
) {
536 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
537 it
!= textfield_
.end();
540 std::vector
< std::pair
<uint32_t, uint32_t> > segs
;
541 for (uint32_t i
= 0; i
< ev
.GetSegmentNumber(); ++i
)
542 segs
.push_back(std::make_pair(ev
.GetSegmentOffset(i
),
543 ev
.GetSegmentOffset(i
+ 1)));
544 uint32_t selection_start
;
545 uint32_t selection_end
;
546 ev
.GetSelection(&selection_start
, &selection_end
);
547 it
->SetComposition(ev
.GetText().AsString(),
549 ev
.GetTargetSegment(),
550 std::make_pair(selection_start
, selection_end
));
557 bool OnCompositionEnd(const pp::IMEInputEvent
& ev
) {
558 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
559 it
!= textfield_
.end();
562 it
->SetComposition(std::string(),
563 std::vector
<std::pair
<uint32_t, uint32_t> >(),
565 std::make_pair(0, 0));
572 bool OnMouseDown(const pp::MouseInputEvent
& ev
) {
575 bool anyone_focused
= false;
576 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
577 it
!= textfield_
.end();
579 if (it
->RefocusByMouseClick(ev
.GetPosition().x(),
580 ev
.GetPosition().y())) {
581 anyone_focused
= true;
585 status_handler_
->FocusOut();
589 bool OnMouseMove(const pp::MouseInputEvent
& ev
) {
590 const PPB_CursorControl_Dev
* cursor_control
=
591 reinterpret_cast<const PPB_CursorControl_Dev
*>(
592 pp::Module::Get()->GetBrowserInterface(
593 PPB_CURSOR_CONTROL_DEV_INTERFACE
));
597 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
598 it
!= textfield_
.end();
600 if (it
->Contains(ev
.GetPosition().x(),
601 ev
.GetPosition().y())) {
602 cursor_control
->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM
,
604 if (it
->Focused() && dragging_
)
605 it
->MouseDrag(ev
.GetPosition().x(), ev
.GetPosition().y());
609 cursor_control
->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER
,
614 bool OnMouseUp(const pp::MouseInputEvent
& ev
) {
616 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
617 it
!= textfield_
.end();
620 it
->MouseUp(ev
.GetPosition().x(), ev
.GetPosition().y());
624 bool OnKeyDown(const pp::KeyboardInputEvent
& ev
) {
625 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
626 it
!= textfield_
.end();
629 bool shift
= ev
.GetModifiers() & PP_INPUTEVENT_MODIFIER_SHIFTKEY
;
630 switch (ev
.GetKeyCode()) {
650 bool OnChar(const pp::KeyboardInputEvent
& ev
) {
651 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
652 it
!= textfield_
.end();
655 std::string str
= ev
.GetCharacterText().AsString();
656 if (str
!= "\r" && str
!= "\n")
664 bool OnImeText(const pp::IMEInputEvent ev
) {
665 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
666 it
!= textfield_
.end();
669 it
->InsertText(ev
.GetText().AsString());
677 pp::Rect
clip(0, 0, last_size_
.width(), last_size_
.height());
681 void PaintClip(const pp::Rect
& clip
) {
682 pp::ImageData
image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL
, last_size_
, true);
683 pp::Graphics2D
device(this, last_size_
, false);
684 BindGraphics(device
);
686 for (std::vector
<MyTextField
>::iterator it
= textfield_
.begin();
687 it
!= textfield_
.end();
689 it
->PaintOn(&image
, clip
);
692 device
.PaintImageData(image
, pp::Point(0, 0));
693 device
.Flush(pp::CompletionCallback(&OnFlush
, this));
696 static void OnFlush(void* user_data
, int32_t result
) {}
698 // Prints a debug message.
699 void Log(const pp::Var
& value
) {
700 const PPB_Console
* console
= reinterpret_cast<const PPB_Console
*>(
701 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE
));
704 console
->Log(pp_instance(), PP_LOGLEVEL_LOG
, value
.pp_var());
707 // IME Control interface.
708 TextFieldStatusHandler
* status_handler_
;
710 // Remembers the size of this instance.
713 // Holds instances of text fields.
714 std::vector
<MyTextField
> textfield_
;
716 // Whether or not during a drag operation.
720 class MyModule
: public pp::Module
{
721 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
722 return new MyInstance(instance
);
728 Module
* CreateModule() {
729 return new MyModule();