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

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

Powered by Google App Engine
This is Rietveld 408576698