| Index: ppapi/examples/ime/ime.cc
|
| diff --git a/ppapi/examples/ime/ime.cc b/ppapi/examples/ime/ime.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6352fca0e94742fac5da0dc295c0ff2e241f719f
|
| --- /dev/null
|
| +++ b/ppapi/examples/ime/ime.cc
|
| @@ -0,0 +1,582 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <string>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#include "base/scoped_ptr.h"
|
| +#include "ppapi/c/dev/ppb_console_dev.h"
|
| +#include "ppapi/c/dev/ppb_cursor_control_dev.h"
|
| +#include "ppapi/cpp/completion_callback.h"
|
| +#include "ppapi/cpp/dev/font_dev.h"
|
| +#include "ppapi/cpp/dev/text_input_dev.h"
|
| +#include "ppapi/cpp/graphics_2d.h"
|
| +#include "ppapi/cpp/image_data.h"
|
| +#include "ppapi/cpp/input_event.h"
|
| +#include "ppapi/cpp/instance.h"
|
| +#include "ppapi/cpp/module.h"
|
| +#include "ppapi/cpp/rect.h"
|
| +#include "ppapi/cpp/size.h"
|
| +#include "ui/base/keycodes/keyboard_codes.h"
|
| +
|
| +namespace {
|
| +
|
| +static const uint32_t TEXTFIELD_BG_COLOR = 0xffffffff;
|
| +static const uint32_t TEXTFIELD_TEXT_COLOR = 0xff000000;
|
| +static const uint32_t TEXTFIELD_CARET_COLOR = 0xff000000;
|
| +static const uint32_t TEXTFIELD_PREEDIT_TEXT_COLOR = 0xffff0000;
|
| +static const uint32_t TEXTFIELD_UNDERLINE_COLOR_MAIN = 0xffff0000;
|
| +static const uint32_t TEXTFIELD_UNDERLINE_COLOR_SUB = 0xffddaaaa;
|
| +
|
| +void FillRect(pp::ImageData* image,
|
| + int left, int top, int width, int height,
|
| + uint32_t color) {
|
| + for (int y = std::max(0, top);
|
| + y < std::min(image->size().height() - 1, top + height);
|
| + ++y) {
|
| + for (int x = std::max(0, left);
|
| + x < std::min(image->size().width() - 1, left + width);
|
| + ++x)
|
| + *image->GetAddr32(pp::Point(x, y)) = color;
|
| + }
|
| +}
|
| +
|
| +void FillRect(pp::ImageData* image, const pp::Rect& rect, uint32_t color) {
|
| + FillRect(image, rect.x(), rect.y(), rect.width(), rect.height(), color);
|
| +}
|
| +
|
| +size_t utf8_prev(const std::string& str, size_t i) {
|
| + if (i > 0) {
|
| + do
|
| + --i;
|
| + while ((str[i] & 0xC0) == 0x80 && i > 0);
|
| + }
|
| + return i;
|
| +}
|
| +
|
| +size_t utf8_next(const std::string& str, size_t i, size_t n) {
|
| + for (size_t step = 0; step < n; ++step) {
|
| + if (i < str.size()) {
|
| + do
|
| + ++i;
|
| + while ((str[i] & 0xC0) == 0x80 && i < str.size());
|
| + }
|
| + }
|
| + return i;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +class TextFieldStatusHandler {
|
| + public:
|
| + virtual ~TextFieldStatusHandler() {}
|
| + virtual void focusIn(const pp::Rect& caret, const pp::Rect& boundingBox) {}
|
| + virtual void focusOut() {}
|
| +};
|
| +
|
| +class TextFieldStatusNotifyingHanlder : public TextFieldStatusHandler {
|
| + public:
|
| + explicit TextFieldStatusNotifyingHanlder(pp::Instance* instance)
|
| + : instance_(instance),
|
| + textinput_control_(instance) {}
|
| +
|
| + protected:
|
| + virtual void focusIn(const pp::Rect& caret, const pp::Rect& boundingBox) {
|
| + textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_TEXT);
|
| + textinput_control_.UpdateCaretPosition(caret, boundingBox);
|
| + }
|
| + virtual void focusOut() {
|
| + textinput_control_.CancelCompositionText();
|
| + textinput_control_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
|
| + }
|
| +
|
| + private:
|
| + pp::Instance* instance_;
|
| + pp::TextInput_Dev textinput_control_;
|
| +};
|
| +
|
| +// Hand-made text field for demonstrating text input API.
|
| +class MyTextField {
|
| + public:
|
| + MyTextField(pp::Instance* instance, TextFieldStatusHandler* handler,
|
| + int x, int y, int width, int height)
|
| + : instance_(instance),
|
| + status_handler_(handler),
|
| + area_(x, y, width, height),
|
| + font_size_(height-2),
|
| + caret_pos_(std::string::npos) {
|
| + pp::FontDescription_Dev desc;
|
| + desc.set_family(PP_FONTFAMILY_SANSSERIF);
|
| + desc.set_size(font_size_);
|
| + font_ = pp::Font_Dev(instance_, desc);
|
| + }
|
| +
|
| + // Paint on the specified ImageData.
|
| + void PaintOn(pp::ImageData* image, pp::Rect clip) {
|
| + clip = clip.Intersect(area_);
|
| + FillRect(image, clip, TEXTFIELD_BG_COLOR);
|
| +
|
| + if (caret_pos_ != std::string::npos) {
|
| + int offset = area_.x();
|
| + // before caret
|
| + {
|
| + std::string str = utf8_text_.substr(0, caret_pos_);
|
| + font_.DrawTextAt(
|
| + image,
|
| + pp::TextRun_Dev(str.c_str(), false, false),
|
| + pp::Point(offset, area_.y()+font_size_),
|
| + TEXTFIELD_TEXT_COLOR,
|
| + clip,
|
| + false);
|
| + offset += font_.MeasureSimpleText(str);
|
| + }
|
| + // composition
|
| + {
|
| + const std::string& str = composition_;
|
| + font_.DrawTextAt(
|
| + image,
|
| + pp::TextRun_Dev(str.c_str(), false, false),
|
| + pp::Point(offset, area_.y()+font_size_),
|
| + TEXTFIELD_PREEDIT_TEXT_COLOR,
|
| + clip,
|
| + false);
|
| + for (size_t i = 0; i < segments_.size(); ++i) {
|
| + size_t l = segments_[i].first;
|
| + size_t r = segments_[i].second;
|
| + if (l != r) {
|
| + int lx = font_.MeasureSimpleText(str.substr(0, l));
|
| + int rx = font_.MeasureSimpleText(str.substr(0, r));
|
| + FillRect(image,
|
| + offset+lx+2, area_.y()+font_size_+1, (rx-lx)-4, 2,
|
| + i == static_cast<size_t>(targetSegment_) ?
|
| + TEXTFIELD_UNDERLINE_COLOR_MAIN :
|
| + TEXTFIELD_UNDERLINE_COLOR_SUB);
|
| + }
|
| + }
|
| + // caret
|
| + int caretx = font_.MeasureSimpleText(str.substr(0, selection_.first));
|
| + FillRect(image,
|
| + pp::Rect(offset+caretx, area_.y(), 2, area_.height()),
|
| + TEXTFIELD_CARET_COLOR);
|
| + offset += font_.MeasureSimpleText(str);
|
| + }
|
| + // after caret
|
| + {
|
| + std::string str = utf8_text_.substr(caret_pos_);
|
| + font_.DrawTextAt(
|
| + image,
|
| + pp::TextRun_Dev(str.c_str(), false, false),
|
| + pp::Point(offset, area_.y()+font_size_),
|
| + TEXTFIELD_TEXT_COLOR,
|
| + clip,
|
| + false);
|
| + }
|
| + } else {
|
| + font_.DrawTextAt(
|
| + image,
|
| + pp::TextRun_Dev(utf8_text_.c_str(), false, false),
|
| + pp::Point(area_.x(), area_.y()+font_size_),
|
| + TEXTFIELD_TEXT_COLOR,
|
| + clip,
|
| + false);
|
| + }
|
| + }
|
| +
|
| + // Update current composition text.
|
| + void setComposition(
|
| + const std::string& text,
|
| + const std::vector< std::pair<uint32_t, uint32_t> >& segments,
|
| + int32_t target_segment,
|
| + const std::pair<uint32_t, uint32_t>& selection) {
|
| + composition_ = text;
|
| + segments_ = segments;
|
| + targetSegment_ = target_segment;
|
| + selection_ = selection;
|
| + caretPosChanged();
|
| + }
|
| +
|
| + // Am I focused?
|
| + bool focused() const {
|
| + return caret_pos_ != std::string::npos;
|
| + }
|
| +
|
| + // Does the coordinate (x,y) is contained inside the edit box?
|
| + bool contains(int x, int y) const {
|
| + return area_.Contains(x, y);
|
| + }
|
| +
|
| + // reset the content text
|
| + void set_text(const std::string& text) {
|
| + utf8_text_ = text;
|
| + if (focused()) {
|
| + caret_pos_ = text.size();
|
| + caretPosChanged();
|
| + }
|
| + }
|
| +
|
| + // insert the text at the caret position
|
| + void insert_text(const std::string& text) {
|
| + if (!focused())
|
| + return;
|
| + utf8_text_.insert(caret_pos_, text);
|
| + if (focused()) {
|
| + caret_pos_ += text.size();
|
| + caretPosChanged();
|
| + }
|
| + }
|
| +
|
| + // event handling
|
| + bool refocusByMouseClick(int x, int y) {
|
| + if (!contains(x, y)) {
|
| + // unfocus
|
| + caret_pos_ = std::string::npos;
|
| + return false;
|
| + }
|
| +
|
| + // focus
|
| + size_t n = font_.CharacterOffsetForPixel(
|
| + pp::TextRun_Dev(utf8_text_.c_str()), x-area_.x());
|
| + caret_pos_ = utf8_next(utf8_text_, 0, n);
|
| + caretPosChanged();
|
| + return true;
|
| + }
|
| +
|
| + void keyLeft() {
|
| + if (!focused())
|
| + return;
|
| + caret_pos_ = utf8_prev(utf8_text_, caret_pos_);
|
| + caretPosChanged();
|
| + }
|
| +
|
| + void keyRight() {
|
| + if (!focused())
|
| + return;
|
| + caret_pos_ = utf8_next(utf8_text_, caret_pos_, 1);
|
| + caretPosChanged();
|
| + }
|
| +
|
| + void keyDelete() {
|
| + if (!focused())
|
| + return;
|
| + size_t i = utf8_next(utf8_text_, caret_pos_, 1);
|
| + utf8_text_.erase(caret_pos_, i-caret_pos_);
|
| + caretPosChanged();
|
| + }
|
| +
|
| + void keyBackspace() {
|
| + if (!focused() || caret_pos_ == 0)
|
| + return;
|
| + keyLeft();
|
| + keyDelete();
|
| + }
|
| +
|
| + private:
|
| + // Notify the plugin instance that the caret position has changed.
|
| + void caretPosChanged() {
|
| + if (focused()) {
|
| + std::string str = utf8_text_.substr(0, caret_pos_);
|
| + if (!composition_.empty())
|
| + str += composition_.substr(0, selection_.first);
|
| + int px = font_.MeasureSimpleText(str);
|
| + pp::Rect textarea(area_.x()+px, area_.y(), 2, area_.height()+2);
|
| + status_handler_->focusIn(textarea, area_);
|
| + }
|
| + }
|
| +
|
| + pp::Instance* instance_;
|
| + TextFieldStatusHandler* status_handler_;
|
| +
|
| + pp::Rect area_;
|
| + int font_size_;
|
| + pp::Font_Dev font_;
|
| + std::string utf8_text_;
|
| + size_t caret_pos_;
|
| + std::string composition_;
|
| + std::vector< std::pair<uint32_t, uint32_t> > segments_;
|
| + std::pair<uint32_t, uint32_t> selection_;
|
| + int targetSegment_;
|
| +};
|
| +
|
| +class MyInstance : public pp::Instance {
|
| + public:
|
| + explicit MyInstance(PP_Instance instance)
|
| + : pp::Instance(instance),
|
| + status_handler_(new TextFieldStatusHandler) {
|
| + }
|
| +
|
| + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
|
| + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
|
| + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
|
| +
|
| + for (uint32_t i = 0; i < argc; ++i) {
|
| + if (argn[i] == std::string("ime")) {
|
| + if (argv[i] == std::string("no")) {
|
| + // Example of NO-IME plugins (e.g., games).
|
| + // Explicitly turns off the text input feature, and
|
| + // Never call any text input related APIs.
|
| + pp::TextInput_Dev(this).SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
|
| + } else if (argv[i] == std::string("unaware")) {
|
| + // Demonstrating the behavior of IME-unaware plugins.
|
| + // Never call any text input related APIs.
|
| + } else if (argv[i] == std::string("caretmove")) {
|
| + // Demonstrating the behavior of plugins with limited IME support.
|
| + // It notifies updates of caret positions to the browser,
|
| + // but unable to handle inline compositions.
|
| + status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
|
| + } else if (argv[i] == std::string("full")) {
|
| + // Demonstrating the behavior of plugins fully supporting IME.
|
| + // It notifies updates of caret positions to the browser,
|
| + // but handles all text input events by itself.
|
| + status_handler_.reset(new TextFieldStatusNotifyingHanlder(this));
|
| + RequestInputEvents(PP_INPUTEVENT_CLASS_IME);
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +
|
| + textfield_.push_back(MyTextField(this, status_handler_.get(),
|
| + 10, 10, 300, 20));
|
| + textfield_.back().set_text("Hello");
|
| + textfield_.push_back(MyTextField(this, status_handler_.get(),
|
| + 30, 100, 300, 20));
|
| + textfield_.back().set_text("World");
|
| + return true;
|
| + }
|
| +
|
| + protected:
|
| + virtual bool HandleInputEvent(const pp::InputEvent& event) {
|
| + bool ret = false;
|
| + switch (event.GetType()) {
|
| + case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
|
| + const pp::MouseInputEvent mouseEvent(event);
|
| + ret = onMouseDown(mouseEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
|
| + const pp::MouseInputEvent mouseEvent(event);
|
| + ret = onMouseMove(mouseEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_KEYDOWN: {
|
| + Log("Keydown");
|
| + const pp::KeyboardInputEvent keyEvent(event);
|
| + ret = onKeyDown(keyEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_CHAR: {
|
| + const pp::KeyboardInputEvent keyEvent(event);
|
| + Log("Char ["+keyEvent.GetCharacterText().AsString()+"]");
|
| + ret = onChar(keyEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_COMPOSITION_START: {
|
| + const pp::CompositionInputEvent compositionEvent(event);
|
| + Log("CompositionStart ["+compositionEvent.GetText().AsString()+"]");
|
| + ret = true;
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_COMPOSITION_UPDATE: {
|
| + const pp::CompositionInputEvent compositionEvent(event);
|
| + Log("CompositionUpdate ["+compositionEvent.GetText().AsString()+"]");
|
| + ret = onCompositionUpdate(compositionEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_COMPOSITION_END: {
|
| + const pp::CompositionInputEvent compositionEvent(event);
|
| + Log("CompositionEnd ["+compositionEvent.GetText().AsString()+"]");
|
| + ret = onCompositionEnd(compositionEvent);
|
| + break;
|
| + }
|
| + case PP_INPUTEVENT_TYPE_IME_TEXT: {
|
| + const pp::CompositionInputEvent compositionEvent(event);
|
| + Log("ImeText ["+compositionEvent.GetText().AsString()+"]");
|
| + ret = onImeText(compositionEvent);
|
| + break;
|
| + }
|
| + default:
|
| + break;
|
| + }
|
| + if (ret && event.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE)
|
| + Paint();
|
| + return ret;
|
| + }
|
| +
|
| + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
|
| + if (position.size() == last_size_)
|
| + return;
|
| + last_size_ = position.size();
|
| + Paint();
|
| + }
|
| +
|
| + private:
|
| + bool onCompositionUpdate(const pp::CompositionInputEvent& ev) {
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->focused()) {
|
| + it->setComposition(ev.GetText().AsString(),
|
| + ev.GetSegments(),
|
| + ev.GetTargetSegment(),
|
| + ev.GetSelection());
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool onCompositionEnd(const pp::CompositionInputEvent& ev) {
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->focused()) {
|
| + it->setComposition("", std::vector< std::pair<uint32_t, uint32_t> >(),
|
| + 0, std::make_pair(0, 0));
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool onMouseDown(const pp::MouseInputEvent& ev) {
|
| + bool anyoneFocused = false;
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->refocusByMouseClick(ev.GetPosition().x(),
|
| + ev.GetPosition().y())) {
|
| + anyoneFocused = true;
|
| + }
|
| + }
|
| + if (!anyoneFocused)
|
| + status_handler_->focusOut();
|
| + return true;
|
| + }
|
| +
|
| + bool onMouseMove(const pp::MouseInputEvent& ev) {
|
| + const PPB_CursorControl_Dev* cursor_control =
|
| + reinterpret_cast<const PPB_CursorControl_Dev*>(
|
| + pp::Module::Get()->GetBrowserInterface(
|
| + PPB_CURSOR_CONTROL_DEV_INTERFACE));
|
| + if (!cursor_control)
|
| + return false;
|
| +
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->contains(ev.GetPosition().x(),
|
| + ev.GetPosition().y())) {
|
| + cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_IBEAM,
|
| + 0, NULL);
|
| + return true;
|
| + }
|
| + }
|
| + cursor_control->SetCursor(pp_instance(), PP_CURSORTYPE_POINTER,
|
| + 0, NULL);
|
| + return true;
|
| + }
|
| +
|
| + bool onKeyDown(const pp::KeyboardInputEvent& ev) {
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->focused()) {
|
| + switch (ev.GetKeyCode()) {
|
| + case ui::VKEY_LEFT:
|
| + it->keyLeft();
|
| + break;
|
| + case ui::VKEY_RIGHT:
|
| + it->keyRight();
|
| + break;
|
| + case ui::VKEY_DELETE:
|
| + it->keyDelete();
|
| + break;
|
| + case ui::VKEY_BACK:
|
| + it->keyBackspace();
|
| + break;
|
| + }
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool onChar(const pp::KeyboardInputEvent& ev) {
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->focused()) {
|
| + it->insert_text(ev.GetCharacterText().AsString());
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool onImeText(const pp::CompositionInputEvent& ev) {
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + if (it->focused()) {
|
| + it->insert_text(ev.GetText().AsString());
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + void Paint() {
|
| + pp::Rect clip(0, 0, last_size_.height(), last_size_.width());
|
| + PaintClip(clip);
|
| + }
|
| +
|
| + void PaintClip(const pp::Rect& clip) {
|
| + pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, last_size_, true);
|
| + pp::Graphics2D device(this, last_size_, false);
|
| + BindGraphics(device);
|
| +
|
| + for (std::vector<MyTextField>::iterator it = textfield_.begin();
|
| + it != textfield_.end();
|
| + ++it) {
|
| + it->PaintOn(&image, clip);
|
| + }
|
| +
|
| + device.PaintImageData(image, pp::Point(0, 0));
|
| + device.Flush(pp::CompletionCallback(&OnFlush, this));
|
| + }
|
| +
|
| + static void OnFlush(void* user_data, int32_t result) {}
|
| +
|
| + // For debugging purpose.
|
| + void Log(const pp::Var& value) {
|
| + const PPB_Console_Dev* console = reinterpret_cast<const PPB_Console_Dev*>(
|
| + pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_DEV_INTERFACE));
|
| + if (!console)
|
| + return;
|
| + console->Log(pp_instance(), PP_LOGLEVEL_LOG, value.pp_var());
|
| + }
|
| +
|
| + // IME Control interface.
|
| + scoped_ptr<TextFieldStatusHandler> status_handler_;
|
| +
|
| + // Remembers the size of this instance.
|
| + pp::Size last_size_;
|
| +
|
| + // Holds instances of text fields.
|
| + std::vector<MyTextField> textfield_;
|
| +};
|
| +
|
| +class MyModule : public pp::Module {
|
| + virtual pp::Instance* CreateInstance(PP_Instance instance) {
|
| + return new MyInstance(instance);
|
| + }
|
| +};
|
| +
|
| +namespace pp {
|
| +
|
| +Module* CreateModule() {
|
| + return new MyModule();
|
| +}
|
| +
|
| +} // namespace pp
|
|
|