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

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: Fix build dependency. 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 static const uint32_t TEXTFIELD_BG_COLOR = 0xffffffff;
kochi 2011/10/04 08:52:45 I don't think you need 'static' for these consts.
kinaba 2011/10/05 04:43:19 Done. Also changed the names to kThisWay.
28 static const uint32_t TEXTFIELD_TEXT_COLOR = 0xff000000;
29 static const uint32_t TEXTFIELD_CARET_COLOR = 0xff000000;
30 static const uint32_t TEXTFIELD_PREEDIT_TEXT_COLOR = 0xffff0000;
31 static const uint32_t TEXTFIELD_UNDERLINE_COLOR_MAIN = 0xffff0000;
32 static const uint32_t TEXTFIELD_UNDERLINE_COLOR_SUB = 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 utf8_prev(const std::string& str, size_t i) {
yzshen1 2011/10/03 18:28:10 Please use FunctionNameLikeThis. (Line 60 has the
kinaba 2011/10/05 04:43:19 Done. Thanks for pointing out these style nits. I
52 if (i > 0) {
53 do
54 --i;
55 while ((str[i] & 0xC0) == 0x80 && i > 0);
kochi 2011/10/04 08:52:45 minor nit: why uppercase 0xC0? (same for line 65)
kinaba 2011/10/05 04:43:19 Done.
56 }
57 return i;
58 }
59
60 size_t utf8_next(const std::string& str, size_t i, size_t n) {
61 for (size_t step = 0; step < n; ++step) {
yzshen1 2011/10/03 18:28:10 Would you please show me why this loop is necessar
kinaba 2011/10/05 04:43:19 Because I use it for moving n characters forward i
62 if (i < str.size()) {
63 do
64 ++i;
65 while ((str[i] & 0xC0) == 0x80 && i < str.size());
66 }
67 }
68 return i;
69 }
70
71 } // namespace
72
73 class TextFieldStatusHandler {
74 public:
75 virtual ~TextFieldStatusHandler() {}
76 virtual void focusIn(const pp::Rect& caret, const pp::Rect& bounding_box) {}
yzshen1 2011/10/03 18:28:10 Please use MethodNameLikeThis. (There are quite a
kinaba 2011/10/05 04:43:19 Done.
77 virtual void focusOut() {}
78 };
79
80 class TextFieldStatusNotifyingHanlder : public TextFieldStatusHandler {
81 public:
82 explicit TextFieldStatusNotifyingHanlder(pp::Instance* instance)
83 : instance_(instance),
84 textinput_control_(instance) {}
85
86 protected:
87 virtual void focusIn(const pp::Rect& caret, const pp::Rect& bounding_box) {
88 textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT);
89 textinput_control_.UpdateCaretPosition(caret, bounding_box);
90 }
91 virtual void focusOut() {
92 textinput_control_.CancelCompositionText();
93 textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
94 }
95
96 private:
97 pp::Instance* instance_;
98 pp::TextInput_Dev textinput_control_;
99 };
100
101 // Hand-made text field for demonstrating text input API.
102 class MyTextField {
103 public:
104 MyTextField(pp::Instance* instance, TextFieldStatusHandler* handler,
105 int x, int y, int width, int height)
106 : instance_(instance),
107 status_handler_(handler),
108 area_(x, y, width, height),
109 font_size_(height-2),
yzshen1 2011/10/03 18:28:10 Spaces around '-', please. (There are quite a few
kinaba 2011/10/05 04:43:19 Done.
110 caret_pos_(std::string::npos) {
111 pp::FontDescription_Dev desc;
112 desc.set_family(PP_FONTFAMILY_SANSSERIF);
113 desc.set_size(font_size_);
114 font_ = pp::Font_Dev(instance_, desc);
115 }
116
117 // Paint on the specified ImageData.
118 void PaintOn(pp::ImageData* image, pp::Rect clip) {
119 clip = clip.Intersect(area_);
120 FillRect(image, clip, TEXTFIELD_BG_COLOR);
121
122 if (caret_pos_ != std::string::npos) {
123 int offset = area_.x();
124 // before caret
125 {
126 std::string str = utf8_text_.substr(0, caret_pos_);
127 font_.DrawTextAt(
128 image,
129 pp::TextRun_Dev(str.c_str(), false, false),
130 pp::Point(offset, area_.y()+font_size_),
131 TEXTFIELD_TEXT_COLOR,
132 clip,
133 false);
134 offset += font_.MeasureSimpleText(str);
135 }
136 // composition
137 {
138 const std::string& str = composition_;
139 font_.DrawTextAt(
140 image,
141 pp::TextRun_Dev(str.c_str(), false, false),
142 pp::Point(offset, area_.y()+font_size_),
143 TEXTFIELD_PREEDIT_TEXT_COLOR,
144 clip,
145 false);
146 for (size_t i = 0; i < segments_.size(); ++i) {
147 size_t l = segments_[i].first;
148 size_t r = segments_[i].second;
149 if (l != r) {
150 int lx = font_.MeasureSimpleText(str.substr(0, l));
151 int rx = font_.MeasureSimpleText(str.substr(0, r));
152 FillRect(image,
153 offset+lx+2, area_.y()+font_size_+1, (rx-lx)-4, 2,
154 i == static_cast<size_t>(targetSegment_) ?
155 TEXTFIELD_UNDERLINE_COLOR_MAIN :
156 TEXTFIELD_UNDERLINE_COLOR_SUB);
157 }
158 }
159 // caret
160 int caretx = font_.MeasureSimpleText(str.substr(0, selection_.first));
161 FillRect(image,
162 pp::Rect(offset+caretx, area_.y(), 2, area_.height()),
163 TEXTFIELD_CARET_COLOR);
164 offset += font_.MeasureSimpleText(str);
165 }
166 // after caret
167 {
168 std::string str = utf8_text_.substr(caret_pos_);
169 font_.DrawTextAt(
170 image,
171 pp::TextRun_Dev(str.c_str(), false, false),
172 pp::Point(offset, area_.y()+font_size_),
173 TEXTFIELD_TEXT_COLOR,
174 clip,
175 false);
176 }
177 } else {
178 font_.DrawTextAt(
179 image,
180 pp::TextRun_Dev(utf8_text_.c_str(), false, false),
181 pp::Point(area_.x(), area_.y()+font_size_),
182 TEXTFIELD_TEXT_COLOR,
183 clip,
184 false);
185 }
186 }
187
188 // Update current composition text.
189 void setComposition(
190 const std::string& text,
191 const std::vector< std::pair<uint32_t, uint32_t> >& segments,
192 int32_t target_segment,
193 const std::pair<uint32_t, uint32_t>& selection) {
194 composition_ = text;
195 segments_ = segments;
196 targetSegment_ = target_segment;
197 selection_ = selection;
198 caretPosChanged();
199 }
200
201 // Am I focused?
202 bool focused() const {
203 return caret_pos_ != std::string::npos;
204 }
205
206 // Does the coordinate (x,y) is contained inside the edit box?
207 bool contains(int x, int y) const {
208 return area_.Contains(x, y);
209 }
210
yzshen1 2011/10/03 18:28:10 Please use sentence, with capital initial and '.'
kinaba 2011/10/05 04:43:19 Done.
211 // reset the content text
212 void set_text(const std::string& text) {
213 utf8_text_ = text;
214 if (focused()) {
215 caret_pos_ = text.size();
216 caretPosChanged();
217 }
218 }
219
220 // insert the text at the caret position
221 void insert_text(const std::string& text) {
222 if (!focused())
223 return;
224 utf8_text_.insert(caret_pos_, text);
225 if (focused()) {
226 caret_pos_ += text.size();
227 caretPosChanged();
228 }
229 }
230
231 // event handling
232 bool refocusByMouseClick(int x, int y) {
233 if (!contains(x, y)) {
234 // unfocus
235 caret_pos_ = std::string::npos;
236 return false;
237 }
238
239 // focus
240 size_t n = font_.CharacterOffsetForPixel(
241 pp::TextRun_Dev(utf8_text_.c_str()), x-area_.x());
242 caret_pos_ = utf8_next(utf8_text_, 0, n);
243 caretPosChanged();
244 return true;
245 }
246
247 void keyLeft() {
248 if (!focused())
249 return;
250 caret_pos_ = utf8_prev(utf8_text_, caret_pos_);
251 caretPosChanged();
252 }
253
254 void keyRight() {
255 if (!focused())
256 return;
257 caret_pos_ = utf8_next(utf8_text_, caret_pos_, 1);
258 caretPosChanged();
259 }
260
261 void keyDelete() {
262 if (!focused())
263 return;
264 size_t i = utf8_next(utf8_text_, caret_pos_, 1);
265 utf8_text_.erase(caret_pos_, i-caret_pos_);
266 caretPosChanged();
267 }
268
269 void keyBackspace() {
270 if (!focused() || caret_pos_ == 0)
271 return;
272 keyLeft();
273 keyDelete();
274 }
275
276 private:
277 // Notify the plugin instance that the caret position has changed.
278 void caretPosChanged() {
279 if (focused()) {
280 std::string str = utf8_text_.substr(0, caret_pos_);
281 if (!composition_.empty())
282 str += composition_.substr(0, selection_.first);
283 int px = font_.MeasureSimpleText(str);
284 pp::Rect caret(area_.x()+px, area_.y(), 0, area_.height()+2);
285 status_handler_->focusIn(caret, area_);
286 }
287 }
288
289 pp::Instance* instance_;
290 TextFieldStatusHandler* status_handler_;
291
292 pp::Rect area_;
293 int font_size_;
294 pp::Font_Dev font_;
295 std::string utf8_text_;
296 size_t caret_pos_;
297 std::string composition_;
298 std::vector< std::pair<uint32_t, uint32_t> > segments_;
299 std::pair<uint32_t, uint32_t> selection_;
300 int targetSegment_;
301 };
302
303 class MyInstance : public pp::Instance {
304 public:
305 explicit MyInstance(PP_Instance instance)
306 : pp::Instance(instance),
307 status_handler_(new TextFieldStatusHandler) {
308 }
309
310 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
311 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
312 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
313
314 for (uint32_t i = 0; i < argc; ++i) {
315 if (argn[i] == std::string("ime")) {
316 if (argv[i] == std::string("no")) {
317 // Example of NO-IME plugins (e.g., games).
318 //
319 // When a plugin never wants to accept text input, at initialization
320 // explicitly turn off the text input feature by calling:
321 pp::TextInput_Dev(this).SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
322 } else if (argv[i] == std::string("unaware")) {
323 // Demonstrating the behavior of IME-unaware plugins.
324 // Never call any text input related APIs.
325 //
326 // In such a case, the plugin is assumed to always accept text input.
327 // For example, when the plugin is focused in touch devices a virtual
328 // keyboard may pop up, or in environment IME is used, users can type
329 // text via IME on the plugin. The characters are delivered to the
330 // plugin via PP_INPUTEVENT_TYPE_CHAR events.
331 } else if (argv[i] == std::string("caretmove")) {
332 // Demonstrating the behavior of plugins with limited IME support.
333 //
334 // It uses SetTextInputType() and UpdateCaretPosition() API to notify
335 // text input status to the browser, but unable to handle inline
336 // compositions. By using the notified information. the browser can,
337 // say, show virtual keyboards or IMEs only at appropriate timing
338 // that the plugin does need to accept text input.
339 status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
340 } else if (argv[i] == std::string("full")) {
341 // Demonstrating the behavior of plugins fully supporting IME.
342 //
343 // It notifies updates of caret positions to the browser,
344 // and handles all text input events by itself.
345 status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
346 RequestInputEvents(PP_INPUTEVENT_CLASS_IME);
347 }
348 break;
349 }
350 }
351
352 textfield_.push_back(MyTextField(this, status_handler_.get(),
353 10, 10, 300, 20));
354 textfield_.back().set_text("Hello");
355 textfield_.push_back(MyTextField(this, status_handler_.get(),
356 30, 100, 300, 20));
357 textfield_.back().set_text("World");
358 return true;
359 }
360
361 protected:
362 virtual bool HandleInputEvent(const pp::InputEvent& event) {
363 bool ret = false;
364 switch (event.GetType()) {
365 case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
366 const pp::MouseInputEvent mouseEvent(event);
367 ret = onMouseDown(mouseEvent);
368 break;
369 }
370 case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
371 const pp::MouseInputEvent mouseEvent(event);
372 ret = onMouseMove(mouseEvent);
373 break;
374 }
375 case PP_INPUTEVENT_TYPE_KEYDOWN: {
376 Log("Keydown");
377 const pp::KeyboardInputEvent keyEvent(event);
378 ret = onKeyDown(keyEvent);
379 break;
380 }
381 case PP_INPUTEVENT_TYPE_CHAR: {
382 const pp::KeyboardInputEvent keyEvent(event);
383 Log("Char ["+keyEvent.GetCharacterText().AsString()+"]");
384 ret = onChar(keyEvent);
385 break;
386 }
387 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: {
388 const pp::IMEInputEvent_Dev imeEvent(event);
389 Log("CompositionStart ["+imeEvent.GetText().AsString()+"]");
390 ret = true;
391 break;
392 }
393 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: {
394 const pp::IMEInputEvent_Dev imeEvent(event);
395 Log("CompositionUpdate ["+imeEvent.GetText().AsString()+"]");
396 ret = onCompositionUpdate(imeEvent);
397 break;
398 }
399 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: {
400 const pp::IMEInputEvent_Dev imeEvent(event);
401 Log("CompositionEnd ["+imeEvent.GetText().AsString()+"]");
402 ret = onCompositionEnd(imeEvent);
403 break;
404 }
405 case PP_INPUTEVENT_TYPE_IME_TEXT: {
406 const pp::IMEInputEvent_Dev imeEvent(event);
407 Log("ImeText ["+imeEvent.GetText().AsString()+"]");
408 ret = onImeText(imeEvent);
409 break;
410 }
411 default:
412 break;
413 }
414 if (ret && event.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE)
415 Paint();
416 return ret;
417 }
418
419 virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
420 if (position.size() == last_size_)
421 return;
422 last_size_ = position.size();
423 Paint();
424 }
425
426 private:
427 bool onCompositionUpdate(const pp::IMEInputEvent_Dev ev) {
yzshen1 2011/10/03 18:28:10 May consider to use const & (and also line 446).
kinaba 2011/10/05 04:43:19 Done.
428 for (std::vector<MyTextField>::iterator it = textfield_.begin();
429 it != textfield_.end();
430 ++it) {
431 if (it->focused()) {
432 std::vector< std::pair<uint32_t, uint32_t> > segs;
433 for (uint32_t i=0; i<ev.GetSegmentNumber(); ++i)
434 segs.push_back( std::make_pair(ev.GetSegmentOffset(i),
435 ev.GetSegmentOffset(i+1)) );
436 it->setComposition(ev.GetText().AsString(),
437 segs,
438 ev.GetTargetSegment(),
439 ev.GetSelection());
440 return true;
441 }
442 }
443 return false;
444 }
445
446 bool onCompositionEnd(const pp::IMEInputEvent_Dev ev) {
447 for (std::vector<MyTextField>::iterator it = textfield_.begin();
448 it != textfield_.end();
449 ++it) {
450 if (it->focused()) {
451 it->setComposition("", std::vector< std::pair<uint32_t, uint32_t> >(),
452 0, std::make_pair(0, 0));
453 return true;
454 }
455 }
456 return false;
457 }
458
459 bool onMouseDown(const pp::MouseInputEvent& ev) {
460 bool anyoneFocused = false;
yzshen1 2011/10/03 18:28:10 Please use variable_name_like_this.
kinaba 2011/10/05 04:43:19 Done.
461 for (std::vector<MyTextField>::iterator it = textfield_.begin();
462 it != textfield_.end();
463 ++it) {
464 if (it->refocusByMouseClick(ev.GetPosition().x(),
465 ev.GetPosition().y())) {
466 anyoneFocused = true;
467 }
468 }
469 if (!anyoneFocused)
470 status_handler_->focusOut();
471 return true;
472 }
473
474 bool onMouseMove(const pp::MouseInputEvent& ev) {
475 const PPB_CursorControl_Dev* cursor_control =
476 reinterpret_cast<const PPB_CursorControl_Dev*>(
477 pp::Module::Get()->GetBrowserInterface(
478 PPB_CURSOR_CONTROL_DEV_INTERFACE));
479 if (!cursor_control)
480 return false;
481
482 for (std::vector<MyTextField>::iterator it = textfield_.begin();
483 it != textfield_.end();
484 ++it) {
485 if (it->contains(ev.GetPosition().x(),
486 ev.GetPosition().y())) {
487 cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM,
488 0, NULL);
489 return true;
490 }
491 }
492 cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER,
493 0, NULL);
494 return true;
495 }
496
497 bool onKeyDown(const pp::KeyboardInputEvent& ev) {
498 for (std::vector<MyTextField>::iterator it = textfield_.begin();
499 it != textfield_.end();
500 ++it) {
501 if (it->focused()) {
502 switch (ev.GetKeyCode()) {
503 case ui::VKEY_LEFT:
504 it->keyLeft();
505 break;
506 case ui::VKEY_RIGHT:
507 it->keyRight();
508 break;
509 case ui::VKEY_DELETE:
510 it->keyDelete();
511 break;
512 case ui::VKEY_BACK:
513 it->keyBackspace();
514 break;
515 }
516 return true;
517 }
518 }
519 return false;
520 }
521
522 bool onChar(const pp::KeyboardInputEvent& ev) {
523 for (std::vector<MyTextField>::iterator it = textfield_.begin();
524 it != textfield_.end();
525 ++it) {
526 if (it->focused()) {
527 std::string str = ev.GetCharacterText().AsString();
528 if (str != "\r" && str != "\n")
529 it->insert_text(str);
530 return true;
531 }
532 }
533 return false;
534 }
535
536 bool onImeText(const pp::IMEInputEvent_Dev ev) {
537 for (std::vector<MyTextField>::iterator it = textfield_.begin();
538 it != textfield_.end();
539 ++it) {
540 if (it->focused()) {
541 it->insert_text(ev.GetText().AsString());
542 return true;
543 }
544 }
545 return false;
546 }
547
548 void Paint() {
549 pp::Rect clip(0, 0, last_size_.width(), last_size_.height());
550 PaintClip(clip);
551 }
552
553 void PaintClip(const pp::Rect& clip) {
554 pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, last_size_, true);
555 pp::Graphics2D device(this, last_size_, false);
556 BindGraphics(device);
557
558 for (std::vector<MyTextField>::iterator it = textfield_.begin();
559 it != textfield_.end();
560 ++it) {
561 it->PaintOn(&image, clip);
562 }
563
564 device.PaintImageData(image, pp::Point(0, 0));
565 device.Flush(pp::CompletionCallback(&OnFlush, this));
566 }
567
568 static void OnFlush(void* user_data, int32_t result) {}
569
570 // For debugging purpose.
571 void Log(const pp::Var& value) {
572 const PPB_Console_Dev* console = reinterpret_cast<const PPB_Console_Dev*>(
573 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_DEV_INTERFACE));
574 if (!console)
575 return;
576 console->Log(pp_instance(), PP_LOGLEVEL_LOG, value.pp_var());
577 }
578
579 // IME Control interface.
580 scoped_ptr<TextFieldStatusHandler> status_handler_;
581
582 // Remembers the size of this instance.
583 pp::Size last_size_;
584
585 // Holds instances of text fields.
586 std::vector<MyTextField> textfield_;
587 };
588
589 class MyModule : public pp::Module {
590 virtual pp::Instance* CreateInstance(PP_Instance instance) {
591 return new MyInstance(instance);
592 }
593 };
594
595 namespace pp {
596
597 Module* CreateModule() {
598 return new MyModule();
599 }
600
601 } // namespace pp
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698