Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: ppapi/examples/ime/ime.cc

Issue 8073021: Implement Pepper IME API. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge trunk. Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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.
4
5 #include <string>
6 #include <utility>
7 #include <vector>
8
9 #include "base/memory/scoped_ptr.h"
10 #include "ppapi/c/dev/ppb_console_dev.h"
11 #include "ppapi/c/dev/ppb_cursor_control_dev.h"
12 #include "ppapi/cpp/completion_callback.h"
13 #include "ppapi/cpp/dev/font_dev.h"
14 #include "ppapi/cpp/dev/ime_input_event_dev.h"
15 #include "ppapi/cpp/dev/text_input_dev.h"
16 #include "ppapi/cpp/graphics_2d.h"
17 #include "ppapi/cpp/image_data.h"
18 #include "ppapi/cpp/input_event.h"
19 #include "ppapi/cpp/instance.h"
20 #include "ppapi/cpp/module.h"
21 #include "ppapi/cpp/rect.h"
22 #include "ppapi/cpp/size.h"
23 #include "ui/base/keycodes/keyboard_codes.h"
24
25 namespace {
26
27 const uint32_t kTextfieldBgColor = 0xffffffff;
28 const uint32_t kTextfieldTextColor = 0xff000000;
29 const uint32_t kTextfieldCaretColor = 0xff000000;
30 const uint32_t kTextfieldPreeditTextColor = 0xffff0000;
31 const uint32_t kTextfieldUnderlineColorMain = 0xffff0000;
32 const uint32_t kTextfieldUnderlineColorSub = 0xffddaaaa;
33
34 void FillRect(pp::ImageData* image,
35 int left, int top, int width, int height,
36 uint32_t color) {
37 for (int y = std::max(0, top);
38 y < std::min(image->size().height() - 1, top + height);
39 ++y) {
40 for (int x = std::max(0, left);
41 x < std::min(image->size().width() - 1, left + width);
42 ++x)
43 *image->GetAddr32(pp::Point(x, y)) = color;
44 }
45 }
46
47 void FillRect(pp::ImageData* image, const pp::Rect& rect, uint32_t color) {
48 FillRect(image, rect.x(), rect.y(), rect.width(), rect.height(), color);
49 }
50
51 size_t GetPrevCharOffsetUtf8(const std::string& str, size_t current_pos) {
52 size_t i = current_pos;
53 if (i > 0) {
54 do
55 --i;
56 while (i > 0 && (str[i] & 0xc0) == 0x80);
57 }
58 return i;
59 }
60
61 size_t GetNextCharOffsetUtf8(const std::string& str, size_t current_pos) {
62 size_t i = current_pos;
63 if (i < str.size()) {
64 do
65 ++i;
66 while (i < str.size() && (str[i] & 0xc0) == 0x80);
67 }
68 return i;
69 }
70
71 size_t GetNthCharOffsetUtf8(const std::string& str, size_t n) {
72 size_t i = 0;
73 for (size_t step = 0; step < n; ++step)
74 i = GetNextCharOffsetUtf8(str, i);
75 return i;
76 }
77
78 } // namespace
79
80 class TextFieldStatusHandler {
81 public:
82 virtual ~TextFieldStatusHandler() {}
83 virtual void FocusIn(const pp::Rect& caret, const pp::Rect& bounding_box) {}
84 virtual void FocusOut() {}
85 };
86
87 class TextFieldStatusNotifyingHanlder : public TextFieldStatusHandler {
88 public:
89 explicit TextFieldStatusNotifyingHanlder(pp::Instance* instance)
90 : instance_(instance),
91 textinput_control_(instance) {}
92
93 protected:
94 virtual void FocusIn(const pp::Rect& caret, const pp::Rect& bounding_box) {
95 textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT);
96 textinput_control_.UpdateCaretPosition(caret, bounding_box);
97 }
98 virtual void FocusOut() {
99 textinput_control_.CancelCompositionText();
100 textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
101 }
102
103 private:
104 pp::Instance* instance_;
105 pp::TextInput_Dev textinput_control_;
106 };
107
108 // Hand-made text field for demonstrating text input API.
109 class MyTextField {
110 public:
111 MyTextField(pp::Instance* instance, TextFieldStatusHandler* handler,
112 int x, int y, int width, int height)
113 : instance_(instance),
114 status_handler_(handler),
115 area_(x, y, width, height),
116 font_size_(height - 2),
117 caret_pos_(std::string::npos) {
118 pp::FontDescription_Dev desc;
119 desc.set_family(PP_FONTFAMILY_SANSSERIF);
120 desc.set_size(font_size_);
121 font_ = pp::Font_Dev(instance_, desc);
122 }
123
124 // Paint on the specified ImageData.
125 void PaintOn(pp::ImageData* image, pp::Rect clip) {
126 clip = clip.Intersect(area_);
127 FillRect(image, clip, kTextfieldBgColor);
128
129 if (caret_pos_ != std::string::npos) {
130 int offset = area_.x();
131 // before caret
132 {
133 std::string str = utf8_text_.substr(0, caret_pos_);
134 font_.DrawTextAt(
135 image,
136 pp::TextRun_Dev(str.c_str(), false, false),
137 pp::Point(offset, area_.y() + font_size_),
138 kTextfieldTextColor,
139 clip,
140 false);
141 offset += font_.MeasureSimpleText(str);
142 }
143 // composition
144 {
145 const std::string& str = composition_;
146 font_.DrawTextAt(
147 image,
148 pp::TextRun_Dev(str.c_str(), false, false),
149 pp::Point(offset, area_.y() + font_size_),
150 kTextfieldPreeditTextColor,
151 clip,
152 false);
153 for (size_t i = 0; i < segments_.size(); ++i) {
154 size_t l = segments_[i].first;
155 size_t r = segments_[i].second;
156 if (l != r) {
157 int lx = font_.MeasureSimpleText(str.substr(0, l));
158 int rx = font_.MeasureSimpleText(str.substr(0, r));
159 FillRect(image,
160 offset + lx + 2, area_.y() + font_size_ + 1,
161 rx - lx - 4, 2,
162 i == static_cast<size_t>(target_segment_) ?
163 kTextfieldUnderlineColorMain :
164 kTextfieldUnderlineColorSub);
165 }
166 }
167 // caret
168 int caretx = font_.MeasureSimpleText(str.substr(0, selection_.first));
169 FillRect(image,
170 pp::Rect(offset + caretx, area_.y(), 2, area_.height()),
171 kTextfieldCaretColor);
172 offset += font_.MeasureSimpleText(str);
173 }
174 // after caret
175 {
176 std::string str = utf8_text_.substr(caret_pos_);
177 font_.DrawTextAt(
178 image,
179 pp::TextRun_Dev(str.c_str(), false, false),
180 pp::Point(offset, area_.y() + font_size_),
181 kTextfieldTextColor,
182 clip,
183 false);
184 }
185 } else {
186 font_.DrawTextAt(
187 image,
188 pp::TextRun_Dev(utf8_text_.c_str(), false, false),
189 pp::Point(area_.x(), area_.y() + font_size_),
190 kTextfieldTextColor,
191 clip,
192 false);
193 }
194 }
195
196 // Update current composition text.
197 void SetComposition(
198 const std::string& text,
199 const std::vector< std::pair<uint32_t, uint32_t> >& segments,
200 int32_t target_segment,
201 const std::pair<uint32_t, uint32_t>& selection) {
202 composition_ = text;
203 segments_ = segments;
204 target_segment_ = target_segment;
205 selection_ = selection;
206 CaretPosChanged();
207 }
208
209 // Is the text field focused?
210 bool Focused() const {
211 return caret_pos_ != std::string::npos;
212 }
213
214 // Does the coordinate (x,y) is contained inside the edit box?
215 bool Contains(int x, int y) const {
216 return area_.Contains(x, y);
217 }
218
219 // Resets the content text.
220 void SetText(const std::string& text) {
221 utf8_text_ = text;
222 if (Focused()) {
223 caret_pos_ = text.size();
224 CaretPosChanged();
225 }
226 }
227
228 // Inserts a text at the current caret position.
229 void InsertText(const std::string& text) {
230 if (!Focused())
231 return;
232 utf8_text_.insert(caret_pos_, text);
233 if (Focused()) {
234 caret_pos_ += text.size();
235 CaretPosChanged();
236 }
237 }
238
239 // Handles mouse click event and changes the focus state.
240 bool RefocusByMouseClick(int x, int y) {
241 if (!Contains(x, y)) {
242 // The text field is unfocused.
243 caret_pos_ = std::string::npos;
244 return false;
245 }
246
247 // The text field is focused.
248 size_t n = font_.CharacterOffsetForPixel(
249 pp::TextRun_Dev(utf8_text_.c_str()), x - area_.x());
250 caret_pos_ = GetNthCharOffsetUtf8(utf8_text_, n);
251 CaretPosChanged();
252 return true;
253 }
254
255 void KeyLeft() {
256 if (!Focused())
257 return;
258 caret_pos_ = GetPrevCharOffsetUtf8(utf8_text_, caret_pos_);
259 CaretPosChanged();
260 }
261
262 void KeyRight() {
263 if (!Focused())
264 return;
265 caret_pos_ = GetNextCharOffsetUtf8(utf8_text_, caret_pos_);
266 CaretPosChanged();
267 }
268
269 void KeyDelete() {
270 if (!Focused())
271 return;
272 size_t i = GetNextCharOffsetUtf8(utf8_text_, caret_pos_);
273 utf8_text_.erase(caret_pos_, i - caret_pos_);
274 CaretPosChanged();
275 }
276
277 void KeyBackspace() {
278 if (!Focused() || caret_pos_ == 0)
279 return;
280 KeyLeft();
281 KeyDelete();
282 }
283
284 private:
285 // Notify the plugin instance that the caret position has changed.
286 void CaretPosChanged() {
287 if (Focused()) {
288 std::string str = utf8_text_.substr(0, caret_pos_);
289 if (!composition_.empty())
290 str += composition_.substr(0, selection_.first);
291 int px = font_.MeasureSimpleText(str);
292 pp::Rect caret(area_.x() + px, area_.y(), 0, area_.height() + 2);
293 status_handler_->FocusIn(caret, area_);
294 }
295 }
296
297 pp::Instance* instance_;
298 TextFieldStatusHandler* status_handler_;
299
300 pp::Rect area_;
301 int font_size_;
302 pp::Font_Dev font_;
303 std::string utf8_text_;
304 size_t caret_pos_;
305 std::string composition_;
306 std::vector< std::pair<uint32_t, uint32_t> > segments_;
307 std::pair<uint32_t, uint32_t> selection_;
308 int target_segment_;
309 };
310
311 class MyInstance : public pp::Instance {
312 public:
313 explicit MyInstance(PP_Instance instance)
314 : pp::Instance(instance),
315 status_handler_(new TextFieldStatusHandler) {
316 }
317
318 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
319 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
320 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
321
322 for (uint32_t i = 0; i < argc; ++i) {
323 if (argn[i] == std::string("ime")) {
324 if (argv[i] == std::string("no")) {
325 // Example of NO-IME plugins (e.g., games).
326 //
327 // When a plugin never wants to accept text input, at initialization
328 // explicitly turn off the text input feature by calling:
329 pp::TextInput_Dev(this).SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
330 } else if (argv[i] == std::string("unaware")) {
331 // Demonstrating the behavior of IME-unaware plugins.
332 // Never call any text input related APIs.
333 //
334 // In such a case, the plugin is assumed to always accept text input.
335 // For example, when the plugin is focused in touch devices a virtual
336 // keyboard may pop up, or in environment IME is used, users can type
337 // text via IME on the plugin. The characters are delivered to the
338 // plugin via PP_INPUTEVENT_TYPE_CHAR events.
339 } else if (argv[i] == std::string("caretmove")) {
340 // Demonstrating the behavior of plugins with limited IME support.
341 //
342 // It uses SetTextInputType() and UpdateCaretPosition() API to notify
343 // text input status to the browser, but unable to handle inline
344 // compositions. By using the notified information. the browser can,
345 // say, show virtual keyboards or IMEs only at appropriate timing
346 // that the plugin does need to accept text input.
347 status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
348 } else if (argv[i] == std::string("full")) {
349 // Demonstrating the behavior of plugins fully supporting IME.
350 //
351 // It notifies updates of caret positions to the browser,
352 // and handles all text input events by itself.
353 status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
354 RequestInputEvents(PP_INPUTEVENT_CLASS_IME);
355 }
356 break;
357 }
358 }
359
360 textfield_.push_back(MyTextField(this, status_handler_.get(),
361 10, 10, 300, 20));
362 textfield_.back().SetText("Hello");
363 textfield_.push_back(MyTextField(this, status_handler_.get(),
364 30, 100, 300, 20));
365 textfield_.back().SetText("World");
366 return true;
367 }
368
369 protected:
370 virtual bool HandleInputEvent(const pp::InputEvent& event) {
371 bool ret = false;
372 switch (event.GetType()) {
373 case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
374 const pp::MouseInputEvent mouseEvent(event);
375 ret = OnMouseDown(mouseEvent);
376 break;
377 }
378 case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
379 const pp::MouseInputEvent mouseEvent(event);
380 ret = OnMouseMove(mouseEvent);
381 break;
382 }
383 case PP_INPUTEVENT_TYPE_KEYDOWN: {
384 Log("Keydown");
385 const pp::KeyboardInputEvent keyEvent(event);
386 ret = OnKeyDown(keyEvent);
387 break;
388 }
389 case PP_INPUTEVENT_TYPE_CHAR: {
390 const pp::KeyboardInputEvent keyEvent(event);
391 Log("Char [" + keyEvent.GetCharacterText().AsString() + "]");
392 ret = OnChar(keyEvent);
393 break;
394 }
395 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: {
396 const pp::IMEInputEvent_Dev imeEvent(event);
397 Log("CompositionStart [" + imeEvent.GetText().AsString() + "]");
398 ret = true;
399 break;
400 }
401 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: {
402 const pp::IMEInputEvent_Dev imeEvent(event);
403 Log("CompositionUpdate [" + imeEvent.GetText().AsString() + "]");
404 ret = OnCompositionUpdate(imeEvent);
405 break;
406 }
407 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: {
408 const pp::IMEInputEvent_Dev imeEvent(event);
409 Log("CompositionEnd [" + imeEvent.GetText().AsString() + "]");
410 ret = OnCompositionEnd(imeEvent);
411 break;
412 }
413 case PP_INPUTEVENT_TYPE_IME_TEXT: {
414 const pp::IMEInputEvent_Dev imeEvent(event);
415 Log("ImeText [" + imeEvent.GetText().AsString() + "]");
416 ret = OnImeText(imeEvent);
417 break;
418 }
419 default:
420 break;
421 }
422 if (ret && event.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE)
423 Paint();
424 return ret;
425 }
426
427 virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
428 if (position.size() == last_size_)
429 return;
430 last_size_ = position.size();
431 Paint();
432 }
433
434 private:
435 bool OnCompositionUpdate(const pp::IMEInputEvent_Dev& ev) {
436 for (std::vector<MyTextField>::iterator it = textfield_.begin();
437 it != textfield_.end();
438 ++it) {
439 if (it->Focused()) {
440 std::vector< std::pair<uint32_t, uint32_t> > segs;
441 for (uint32_t i = 0; i < ev.GetSegmentNumber(); ++i)
442 segs.push_back(std::make_pair(ev.GetSegmentOffset(i),
443 ev.GetSegmentOffset(i + 1)));
444 it->SetComposition(ev.GetText().AsString(),
445 segs,
446 ev.GetTargetSegment(),
447 ev.GetSelection());
448 return true;
449 }
450 }
451 return false;
452 }
453
454 bool OnCompositionEnd(const pp::IMEInputEvent_Dev& ev) {
455 for (std::vector<MyTextField>::iterator it = textfield_.begin();
456 it != textfield_.end();
457 ++it) {
458 if (it->Focused()) {
459 it->SetComposition("", std::vector< std::pair<uint32_t, uint32_t> >(),
460 0, std::make_pair(0, 0));
461 return true;
462 }
463 }
464 return false;
465 }
466
467 bool OnMouseDown(const pp::MouseInputEvent& ev) {
468 bool anyone_focused = false;
469 for (std::vector<MyTextField>::iterator it = textfield_.begin();
470 it != textfield_.end();
471 ++it) {
472 if (it->RefocusByMouseClick(ev.GetPosition().x(),
473 ev.GetPosition().y())) {
474 anyone_focused = true;
475 }
476 }
477 if (!anyone_focused)
478 status_handler_->FocusOut();
479 return true;
480 }
481
482 bool OnMouseMove(const pp::MouseInputEvent& ev) {
483 const PPB_CursorControl_Dev* cursor_control =
484 reinterpret_cast<const PPB_CursorControl_Dev*>(
485 pp::Module::Get()->GetBrowserInterface(
486 PPB_CURSOR_CONTROL_DEV_INTERFACE));
487 if (!cursor_control)
488 return false;
489
490 for (std::vector<MyTextField>::iterator it = textfield_.begin();
491 it != textfield_.end();
492 ++it) {
493 if (it->Contains(ev.GetPosition().x(),
494 ev.GetPosition().y())) {
495 cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM,
496 0, NULL);
497 return true;
498 }
499 }
500 cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER,
501 0, NULL);
502 return true;
503 }
504
505 bool OnKeyDown(const pp::KeyboardInputEvent& ev) {
506 for (std::vector<MyTextField>::iterator it = textfield_.begin();
507 it != textfield_.end();
508 ++it) {
509 if (it->Focused()) {
510 switch (ev.GetKeyCode()) {
511 case ui::VKEY_LEFT:
512 it->KeyLeft();
513 break;
514 case ui::VKEY_RIGHT:
515 it->KeyRight();
516 break;
517 case ui::VKEY_DELETE:
518 it->KeyDelete();
519 break;
520 case ui::VKEY_BACK:
521 it->KeyBackspace();
522 break;
523 }
524 return true;
525 }
526 }
527 return false;
528 }
529
530 bool OnChar(const pp::KeyboardInputEvent& ev) {
531 for (std::vector<MyTextField>::iterator it = textfield_.begin();
532 it != textfield_.end();
533 ++it) {
534 if (it->Focused()) {
535 std::string str = ev.GetCharacterText().AsString();
536 if (str != "\r" && str != "\n")
537 it->InsertText(str);
538 return true;
539 }
540 }
541 return false;
542 }
543
544 bool OnImeText(const pp::IMEInputEvent_Dev ev) {
545 for (std::vector<MyTextField>::iterator it = textfield_.begin();
546 it != textfield_.end();
547 ++it) {
548 if (it->Focused()) {
549 it->InsertText(ev.GetText().AsString());
550 return true;
551 }
552 }
553 return false;
554 }
555
556 void Paint() {
557 pp::Rect clip(0, 0, last_size_.width(), last_size_.height());
558 PaintClip(clip);
559 }
560
561 void PaintClip(const pp::Rect& clip) {
562 pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, last_size_, true);
563 pp::Graphics2D device(this, last_size_, false);
564 BindGraphics(device);
565
566 for (std::vector<MyTextField>::iterator it = textfield_.begin();
567 it != textfield_.end();
568 ++it) {
569 it->PaintOn(&image, clip);
570 }
571
572 device.PaintImageData(image, pp::Point(0, 0));
573 device.Flush(pp::CompletionCallback(&OnFlush, this));
574 }
575
576 static void OnFlush(void* user_data, int32_t result) {}
577
578 // Prints a debug message.
579 void Log(const pp::Var& value) {
580 const PPB_Console_Dev* console = reinterpret_cast<const PPB_Console_Dev*>(
581 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_DEV_INTERFACE));
582 if (!console)
583 return;
584 console->Log(pp_instance(), PP_LOGLEVEL_LOG, value.pp_var());
585 }
586
587 // IME Control interface.
588 scoped_ptr<TextFieldStatusHandler> status_handler_;
589
590 // Remembers the size of this instance.
591 pp::Size last_size_;
592
593 // Holds instances of text fields.
594 std::vector<MyTextField> textfield_;
595 };
596
597 class MyModule : public pp::Module {
598 virtual pp::Instance* CreateInstance(PP_Instance instance) {
599 return new MyInstance(instance);
600 }
601 };
602
603 namespace pp {
604
605 Module* CreateModule() {
606 return new MyModule();
607 }
608
609 } // namespace pp
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698