From be98edc4db0dfdc9c8b13d715c015f447b5969e2 Mon Sep 17 00:00:00 2001 From: "mukai@chromium.org" Date: Wed, 15 Jan 2014 16:24:24 +0000 Subject: Moves IME's views specific code out from chrome/browser/chromeos. Now UI code of input methods -- candidate window, infolist window, and mode indicator -- is pure views and doesn't need to be in c/b. I chose ash/ime because they are ash-specific UI, but I'm okay to move to another place. Please tell me if you have other ideas. BUG=325813 R=oshima@chromium.org, komatsu@chromium.org TEST=no functional changes, compile succeeds Review URL: https://codereview.chromium.org/132083009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@244895 0039d316-1c4b-4281-b951-d872f2087c98 --- ash/ash.gyp | 9 + ash/ash_strings.grd | 4 + ash/ime/OWNERS | 3 + ash/ime/candidate_view.cc | 289 +++++++++++++++ ash/ime/candidate_view.h | 73 ++++ ash/ime/candidate_window_constants.h | 42 +++ ash/ime/candidate_window_view.cc | 403 +++++++++++++++++++++ ash/ime/candidate_window_view.h | 134 +++++++ ash/ime/candidate_window_view_unittest.cc | 384 ++++++++++++++++++++ ash/ime/infolist_window.cc | 282 ++++++++++++++ ash/ime/infolist_window.h | 64 ++++ ash/ime/mode_indicator_view.cc | 69 ++++ ash/ime/mode_indicator_view.h | 52 +++ chrome/app/chromeos_strings.grdp | 3 - .../chromeos/input_method/candidate_view.cc | 289 --------------- .../browser/chromeos/input_method/candidate_view.h | 72 ---- .../input_method/candidate_window_constants.h | 42 --- .../candidate_window_controller_impl.cc | 49 +-- .../candidate_window_controller_impl.h | 31 +- .../candidate_window_controller_impl_unittest.cc | 174 --------- .../chromeos/input_method/candidate_window_view.cc | 402 -------------------- .../chromeos/input_method/candidate_window_view.h | 133 ------- .../input_method/candidate_window_view_unittest.cc | 383 -------------------- .../chromeos/input_method/infolist_window.cc | 298 --------------- .../chromeos/input_method/infolist_window.h | 74 ---- .../input_method/mode_indicator_controller.cc | 18 +- .../input_method/mode_indicator_delegate_view.cc | 75 ---- .../input_method/mode_indicator_delegate_view.h | 49 --- chrome/chrome_browser_chromeos.gypi | 8 - chrome/chrome_tests_unit.gypi | 2 - ui/base/ime/candidate_window.cc | 27 ++ ui/base/ime/candidate_window.h | 6 + ui/base/ime/candidate_window_unittest.cc | 119 ++++++ ui/base/ime/ime.gypi | 2 + ui/base/ime/infolist_entry.cc | 22 ++ ui/base/ime/infolist_entry.h | 26 ++ 36 files changed, 2048 insertions(+), 2064 deletions(-) create mode 100644 ash/ime/OWNERS create mode 100644 ash/ime/candidate_view.cc create mode 100644 ash/ime/candidate_view.h create mode 100644 ash/ime/candidate_window_constants.h create mode 100644 ash/ime/candidate_window_view.cc create mode 100644 ash/ime/candidate_window_view.h create mode 100644 ash/ime/candidate_window_view_unittest.cc create mode 100644 ash/ime/infolist_window.cc create mode 100644 ash/ime/infolist_window.h create mode 100644 ash/ime/mode_indicator_view.cc create mode 100644 ash/ime/mode_indicator_view.h delete mode 100644 chrome/browser/chromeos/input_method/candidate_view.cc delete mode 100644 chrome/browser/chromeos/input_method/candidate_view.h delete mode 100644 chrome/browser/chromeos/input_method/candidate_window_constants.h delete mode 100644 chrome/browser/chromeos/input_method/candidate_window_controller_impl_unittest.cc delete mode 100644 chrome/browser/chromeos/input_method/candidate_window_view.cc delete mode 100644 chrome/browser/chromeos/input_method/candidate_window_view.h delete mode 100644 chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc delete mode 100644 chrome/browser/chromeos/input_method/infolist_window.cc delete mode 100644 chrome/browser/chromeos/input_method/infolist_window.h delete mode 100644 chrome/browser/chromeos/input_method/mode_indicator_delegate_view.cc delete mode 100644 chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h create mode 100644 ui/base/ime/infolist_entry.cc create mode 100644 ui/base/ime/infolist_entry.h diff --git a/ash/ash.gyp b/ash/ash.gyp index f18b68f..ecf4275 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -153,6 +153,14 @@ 'host/root_window_host_factory.cc', 'host/root_window_host_factory.h', 'host/root_window_host_factory_win.cc', + 'ime/candidate_view.cc', + 'ime/candidate_view.h', + 'ime/candidate_window_view.cc', + 'ime/candidate_window_view.h', + 'ime/infolist_window.cc', + 'ime/infolist_window.h', + 'ime/mode_indicator_view.cc', + 'ime/mode_indicator_view.h', 'keyboard_uma_event_filter.cc', 'keyboard_uma_event_filter.h', 'launcher/launcher_types.cc', @@ -824,6 +832,7 @@ 'drag_drop/drag_drop_tracker_unittest.cc', 'extended_desktop_unittest.cc', 'focus_cycler_unittest.cc', + 'ime/candidate_window_view_unittest.cc', 'keyboard_overlay/keyboard_overlay_delegate_unittest.cc', 'keyboard_overlay/keyboard_overlay_view_unittest.cc', 'magnifier/magnification_controller_unittest.cc', diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 8c98008..c51d882 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -582,6 +582,10 @@ Press Shift + Alt to switch. Press Control Shift Q twice to quit. + + Information + + Exiting Session diff --git a/ash/ime/OWNERS b/ash/ime/OWNERS new file mode 100644 index 0000000..7224e3b --- /dev/null +++ b/ash/ime/OWNERS @@ -0,0 +1,3 @@ +komatsu@chromium.org +mukai@chromium.org +nona@chromium.org diff --git a/ash/ime/candidate_view.cc b/ash/ime/candidate_view.cc new file mode 100644 index 0000000..652998a --- /dev/null +++ b/ash/ime/candidate_view.cc @@ -0,0 +1,289 @@ +// Copyright 2014 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 "ash/ime/candidate_view.h" + +#include "ash/ime/candidate_window_constants.h" +#include "base/strings/utf_string_conversions.h" +#include "ui/base/ime/candidate_window.h" +#include "ui/gfx/color_utils.h" +#include "ui/native_theme/native_theme.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/controls/label.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace ime { + +namespace { + +// VerticalCandidateLabel is used for rendering candidate text in +// the vertical candidate window. +class VerticalCandidateLabel : public views::Label { + public: + VerticalCandidateLabel() {} + + private: + virtual ~VerticalCandidateLabel() {} + + // Returns the preferred size, but guarantees that the width has at + // least kMinCandidateLabelWidth pixels. + virtual gfx::Size GetPreferredSize() OVERRIDE { + gfx::Size size = Label::GetPreferredSize(); + size.SetToMax(gfx::Size(kMinCandidateLabelWidth, 0)); + size.SetToMin(gfx::Size(kMaxCandidateLabelWidth, size.height())); + return size; + } + + DISALLOW_COPY_AND_ASSIGN(VerticalCandidateLabel); +}; + +// Creates the shortcut label, and returns it (never returns NULL). +// The label text is not set in this function. +views::Label* CreateShortcutLabel( + ui::CandidateWindow::Orientation orientation, + const ui::NativeTheme& theme) { + // Create the shortcut label. The label will be owned by + // |wrapped_shortcut_label|, hence it's deleted when + // |wrapped_shortcut_label| is deleted. + views::Label* shortcut_label = new views::Label; + + if (orientation == ui::CandidateWindow::VERTICAL) { + shortcut_label->SetFontList( + shortcut_label->font_list().DeriveFontListWithSizeDeltaAndStyle( + kFontSizeDelta, gfx::Font::BOLD)); + } else { + shortcut_label->SetFontList( + shortcut_label->font_list().DeriveFontListWithSizeDelta( + kFontSizeDelta)); + } + // TODO(satorux): Maybe we need to use language specific fonts for + // candidate_label, like Chinese font for Chinese input method? + shortcut_label->SetEnabledColor(theme.GetSystemColor( + ui::NativeTheme::kColorId_LabelEnabledColor)); + shortcut_label->SetDisabledColor(theme.GetSystemColor( + ui::NativeTheme::kColorId_LabelDisabledColor)); + + // Setup paddings. + const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6); + const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0); + const gfx::Insets insets = + (orientation == ui::CandidateWindow::VERTICAL ? + kVerticalShortcutLabelInsets : + kHorizontalShortcutLabelInsets); + shortcut_label->set_border(views::Border::CreateEmptyBorder( + insets.top(), insets.left(), insets.bottom(), insets.right())); + + // Add decoration based on the orientation. + if (orientation == ui::CandidateWindow::VERTICAL) { + // Set the background color. + SkColor blackish = color_utils::AlphaBlend( + SK_ColorBLACK, + theme.GetSystemColor(ui::NativeTheme::kColorId_WindowBackground), + 0x40); + SkColor transparent_blakish = color_utils::AlphaBlend( + SK_ColorTRANSPARENT, blackish, 0xE0); + shortcut_label->set_background( + views::Background::CreateSolidBackground(transparent_blakish)); + } + + return shortcut_label; +} + +// Creates the candidate label, and returns it (never returns NULL). +// The label text is not set in this function. +views::Label* CreateCandidateLabel( + ui::CandidateWindow::Orientation orientation) { + views::Label* candidate_label = NULL; + + // Create the candidate label. The label will be added to |this| as a + // child view, hence it's deleted when |this| is deleted. + if (orientation == ui::CandidateWindow::VERTICAL) { + candidate_label = new VerticalCandidateLabel; + } else { + candidate_label = new views::Label; + } + + // Change the font size. + candidate_label->SetFontList( + candidate_label->font_list().DeriveFontListWithSizeDelta(kFontSizeDelta)); + candidate_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + + return candidate_label; +} + +// Creates the annotation label, and return it (never returns NULL). +// The label text is not set in this function. +views::Label* CreateAnnotationLabel( + ui::CandidateWindow::Orientation orientation, + const ui::NativeTheme& theme) { + // Create the annotation label. + views::Label* annotation_label = new views::Label; + + // Change the font size and color. + annotation_label->SetFontList( + annotation_label->font_list().DeriveFontListWithSizeDelta( + kFontSizeDelta)); + annotation_label->SetEnabledColor(theme.GetSystemColor( + ui::NativeTheme::kColorId_LabelDisabledColor)); + annotation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + + return annotation_label; +} + +} // namespace + +CandidateView::CandidateView( + views::ButtonListener* listener, + ui::CandidateWindow::Orientation orientation) + : views::CustomButton(listener), + orientation_(orientation), + shortcut_label_(NULL), + candidate_label_(NULL), + annotation_label_(NULL), + infolist_icon_(NULL) { + set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); + + const ui::NativeTheme& theme = *GetNativeTheme(); + shortcut_label_ = CreateShortcutLabel(orientation, theme); + candidate_label_ = CreateCandidateLabel(orientation); + annotation_label_ = CreateAnnotationLabel(orientation, theme); + + AddChildView(shortcut_label_); + AddChildView(candidate_label_); + AddChildView(annotation_label_); + + if (orientation == ui::CandidateWindow::VERTICAL) { + infolist_icon_ = new views::View; + infolist_icon_->set_background( + views::Background::CreateSolidBackground(theme.GetSystemColor( + ui::NativeTheme::kColorId_FocusedBorderColor))); + AddChildView(infolist_icon_); + } +} + +void CandidateView::GetPreferredWidths(int* shortcut_width, + int* candidate_width) { + *shortcut_width = shortcut_label_->GetPreferredSize().width(); + *candidate_width = candidate_label_->GetPreferredSize().width(); +} + +void CandidateView::SetWidths(int shortcut_width, int candidate_width) { + shortcut_width_ = shortcut_width; + shortcut_label_->SetVisible(shortcut_width_ != 0); + candidate_width_ = candidate_width; +} + +void CandidateView::SetEntry(const ui::CandidateWindow::Entry& entry) { + std::string label = entry.label; + if (!label.empty() && orientation_ != ui::CandidateWindow::VERTICAL) + label += '.'; + shortcut_label_->SetText(base::UTF8ToUTF16(label)); + candidate_label_->SetText(base::UTF8ToUTF16(entry.value)); + annotation_label_->SetText(base::UTF8ToUTF16(entry.annotation)); +} + +void CandidateView::SetInfolistIcon(bool enable) { + if (infolist_icon_) + infolist_icon_->SetVisible(enable); + SchedulePaint(); +} + +void CandidateView::StateChanged() { + shortcut_label_->SetEnabled(state() != STATE_DISABLED); + if (state() == STATE_PRESSED) { + ui::NativeTheme* theme = GetNativeTheme(); + set_background( + views::Background::CreateSolidBackground(theme->GetSystemColor( + ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused))); + set_border(views::Border::CreateSolidBorder( + 1, theme->GetSystemColor( + ui::NativeTheme::kColorId_FocusedBorderColor))); + + // Cancel currently focused one. + for (int i = 0; i < parent()->child_count(); ++i) { + CandidateView* view = + static_cast((parent()->child_at(i))); + if (view != this && view->state() == STATE_PRESSED) + view->SetState(STATE_NORMAL); + } + } else { + set_background(NULL); + set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); + } +} + +bool CandidateView::OnMouseDragged(const ui::MouseEvent& event) { + if (!HitTestPoint(event.location())) { + // Moves the drag target to the sibling view. + gfx::Point location_in_widget(event.location()); + ConvertPointToWidget(this, &location_in_widget); + for (int i = 0; i < parent()->child_count(); ++i) { + views::View* sibling = parent()->child_at(i); + if (sibling == this) + continue; + gfx::Point location_in_sibling(location_in_widget); + ConvertPointFromWidget(sibling, &location_in_sibling); + if (sibling->HitTestPoint(location_in_sibling)) { + GetWidget()->GetRootView()->SetMouseHandler(sibling); + return sibling->OnMouseDragged(event); + } + } + + return false; + } + + return views::CustomButton::OnMouseDragged(event); +} + +void CandidateView::Layout() { + const int padding_width = + orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6; + int x = 0; + shortcut_label_->SetBounds(x, 0, shortcut_width_, height()); + if (shortcut_width_ > 0) + x += shortcut_width_ + padding_width; + candidate_label_->SetBounds(x, 0, candidate_width_, height()); + x += candidate_width_ + padding_width; + + int right = bounds().right(); + if (infolist_icon_ && infolist_icon_->visible()) { + infolist_icon_->SetBounds( + right - kInfolistIndicatorIconWidth - kInfolistIndicatorIconPadding, + kInfolistIndicatorIconPadding, + kInfolistIndicatorIconWidth, + height() - kInfolistIndicatorIconPadding * 2); + right -= kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2; + } + annotation_label_->SetBounds(x, 0, right - x, height()); +} + +gfx::Size CandidateView::GetPreferredSize() { + const int padding_width = + orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6; + gfx::Size size; + if (shortcut_label_->visible()) { + size = shortcut_label_->GetPreferredSize(); + size.SetToMax(gfx::Size(shortcut_width_, 0)); + size.Enlarge(padding_width, 0); + } + gfx::Size candidate_size = candidate_label_->GetPreferredSize(); + candidate_size.SetToMax(gfx::Size(candidate_width_, 0)); + size.Enlarge(candidate_size.width() + padding_width, 0); + size.SetToMax(candidate_size); + if (annotation_label_->visible()) { + gfx::Size annotation_size = annotation_label_->GetPreferredSize(); + size.Enlarge(annotation_size.width() + padding_width, 0); + size.SetToMax(annotation_size); + } + + // Reserves the margin for infolist_icon even if it's not visible. + size.Enlarge( + kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2, 0); + return size; +} + +} // namespace ime +} // namespace ash diff --git a/ash/ime/candidate_view.h b/ash/ime/candidate_view.h new file mode 100644 index 0000000..6f58957 --- /dev/null +++ b/ash/ime/candidate_view.h @@ -0,0 +1,73 @@ +// Copyright 2014 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. + +#ifndef ASH_IME_CANDIDATE_VIEW_H_ +#define ASH_IME_CANDIDATE_VIEW_H_ + +#include "ash/ash_export.h" +#include "base/gtest_prod_util.h" +#include "ui/base/ime/candidate_window.h" +#include "ui/views/controls/button/custom_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/view.h" + +namespace ash { +namespace ime { + +// CandidateView renderes a row of a candidate. +class ASH_EXPORT CandidateView : public views::CustomButton { + public: + CandidateView(views::ButtonListener* listener, + ui::CandidateWindow::Orientation orientation); + virtual ~CandidateView() {} + + void GetPreferredWidths(int* shortcut_width, + int* candidate_width); + + void SetWidths(int shortcut_width, + int candidate_width); + + void SetEntry(const ui::CandidateWindow::Entry& entry); + + // Sets infolist icon. + void SetInfolistIcon(bool enable); + + private: + friend class CandidateWindowViewTest; + FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, ShortcutSettingTest); + + // Overridden from views::CustomButton: + virtual void StateChanged() OVERRIDE; + + // Overridden from View: + virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE; + virtual void Layout() OVERRIDE; + virtual gfx::Size GetPreferredSize() OVERRIDE; + + // The orientation of the candidate view. + ui::CandidateWindow::Orientation orientation_; + + // Views created in the class will be part of tree of |this|, so these + // child views will be deleted when |this| is deleted. + + // The shortcut label renders shortcut numbers like 1, 2, and 3. + views::Label* shortcut_label_; + // The candidate label renders candidates. + views::Label* candidate_label_; + // The annotation label renders annotations. + views::Label* annotation_label_; + + int shortcut_width_; + int candidate_width_; + + // The infolist icon. + views::View* infolist_icon_; + + DISALLOW_COPY_AND_ASSIGN(CandidateView); +}; + +} // namespace ime +} // namespace ash + +#endif // ASH_IME_CANDIDATE_VIEW_H_ diff --git a/ash/ime/candidate_window_constants.h b/ash/ime/candidate_window_constants.h new file mode 100644 index 0000000..f3ec24e --- /dev/null +++ b/ash/ime/candidate_window_constants.h @@ -0,0 +1,42 @@ +// Copyright 2014 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. + +#ifndef ASH_IME_CANDIDATE_WINDOW_CONSTANTS_H_ +#define ASH_IME_CANDIDATE_WINDOW_CONSTANTS_H_ + +namespace ash { +namespace ime { + +// We'll use a bigger font size, so Chinese characters are more readable +// in the candidate window. +const int kFontSizeDelta = 2; + +// Currently the infolist window only supports Japanese font. +#if defined(GOOGLE_CHROME_BUILD) +const char kJapaneseFontName[] = "MotoyaG04Gothic"; +#else +const char kJapaneseFontName[] = "IPAPGothic"; +#endif + +// The minimum width of candidate labels in the vertical candidate +// window. We use this value to prevent the candidate window from being +// too narrow when all candidates are short. +const int kMinCandidateLabelWidth = 100; +// The maximum width of candidate labels in the vertical candidate +// window. We use this value to prevent the candidate window from being +// too wide when one of candidates are long. +const int kMaxCandidateLabelWidth = 500; +// The minimum width of preedit area. We use this value to prevent the +// candidate window from being too narrow when candidate lists are not shown. +const int kMinPreeditAreaWidth = 134; + +// The width of the infolist indicator icon in the candidate window. +const int kInfolistIndicatorIconWidth = 4; +// The padding size of the infolist indicator icon in the candidate window. +const int kInfolistIndicatorIconPadding = 2; + +} // namespace ime +} // namespace ash + +#endif // ASH_IME_CANDIDATE_WINDOW_CONSTANTS_H_ diff --git a/ash/ime/candidate_window_view.cc b/ash/ime/candidate_window_view.cc new file mode 100644 index 0000000..28de0e7 --- /dev/null +++ b/ash/ime/candidate_window_view.cc @@ -0,0 +1,403 @@ +// Copyright 2014 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 "ash/ime/candidate_window_view.h" + +#include + +#include "ash/ime/candidate_view.h" +#include "ash/ime/candidate_window_constants.h" +#include "base/strings/utf_string_conversions.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/screen.h" +#include "ui/native_theme/native_theme.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/bubble/bubble_frame_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/corewm/window_animations.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/fill_layout.h" + +namespace ash { +namespace ime { + +namespace { + +class CandidateWindowBorder : public views::BubbleBorder { + public: + explicit CandidateWindowBorder(gfx::NativeView parent) + : views::BubbleBorder(views::BubbleBorder::TOP_CENTER, + views::BubbleBorder::NO_SHADOW, + SK_ColorTRANSPARENT), + parent_(parent), + offset_(0) { + set_paint_arrow(views::BubbleBorder::PAINT_NONE); + } + virtual ~CandidateWindowBorder() {} + + void set_offset(int offset) { offset_ = offset; } + + private: + // Overridden from views::BubbleBorder: + virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, + const gfx::Size& content_size) const OVERRIDE { + gfx::Rect bounds(content_size); + bounds.set_origin(gfx::Point( + anchor_rect.x() - offset_, + is_arrow_on_top(arrow()) ? + anchor_rect.bottom() : anchor_rect.y() - content_size.height())); + + // It cannot use the normal logic of arrow offset for horizontal offscreen, + // because the arrow must be in the content's edge. But CandidateWindow has + // to be visible even when |anchor_rect| is out of the screen. + gfx::Rect work_area = gfx::Screen::GetNativeScreen()-> + GetDisplayNearestWindow(parent_).work_area(); + if (bounds.right() > work_area.right()) + bounds.set_x(work_area.right() - bounds.width()); + if (bounds.x() < work_area.x()) + bounds.set_x(work_area.x()); + + return bounds; + } + + virtual gfx::Insets GetInsets() const OVERRIDE { + return gfx::Insets(); + } + + gfx::NativeView parent_; + int offset_; + + DISALLOW_COPY_AND_ASSIGN(CandidateWindowBorder); +}; + +// Computes the page index. For instance, if the page size is 9, and the +// cursor is pointing to 13th candidate, the page index will be 1 (2nd +// page, as the index is zero-origin). Returns -1 on error. +int ComputePageIndex(const ui::CandidateWindow& candidate_window) { + if (candidate_window.page_size() > 0) + return candidate_window.cursor_position() / candidate_window.page_size(); + return -1; +} + +} // namespace + +class InformationTextArea : public views::View { + public: + // InformationTextArea's border is drawn as a separator, it should appear + // at either top or bottom. + enum BorderPosition { + TOP, + BOTTOM + }; + + // Specify the alignment and initialize the control. + InformationTextArea(gfx::HorizontalAlignment align, int min_width) + : min_width_(min_width) { + label_ = new views::Label; + label_->SetHorizontalAlignment(align); + label_->set_border(views::Border::CreateEmptyBorder(2, 2, 2, 4)); + + SetLayoutManager(new views::FillLayout()); + AddChildView(label_); + set_background(views::Background::CreateSolidBackground( + color_utils::AlphaBlend(SK_ColorBLACK, + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_WindowBackground), + 0x10))); + } + + // Sets the text alignment. + void SetAlignment(gfx::HorizontalAlignment alignment) { + label_->SetHorizontalAlignment(alignment); + } + + // Sets the displayed text. + void SetText(const std::string& utf8_text) { + label_->SetText(base::UTF8ToUTF16(utf8_text)); + } + + // Sets the border thickness for top/bottom. + void SetBorder(BorderPosition position) { + set_border(views::Border::CreateSolidSidedBorder( + (position == TOP) ? 1 : 0, 0, (position == BOTTOM) ? 1 : 0, 0, + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_MenuBorderColor))); + } + + protected: + virtual gfx::Size GetPreferredSize() OVERRIDE { + gfx::Size size = views::View::GetPreferredSize(); + size.SetToMax(gfx::Size(min_width_, 0)); + return size; + } + + private: + views::Label* label_; + int min_width_; + + DISALLOW_COPY_AND_ASSIGN(InformationTextArea); +}; + +CandidateWindowView::CandidateWindowView(gfx::NativeView parent) + : selected_candidate_index_in_page_(-1), + should_show_at_composition_head_(false), + should_show_upper_side_(false), + was_candidate_window_open_(false) { + set_parent_window(parent); + set_margins(gfx::Insets()); + + // Set the background and the border of the view. + ui::NativeTheme* theme = GetNativeTheme(); + set_background( + views::Background::CreateSolidBackground(theme->GetSystemColor( + ui::NativeTheme::kColorId_WindowBackground))); + set_border(views::Border::CreateSolidBorder( + 1, theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBorderColor))); + + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + auxiliary_text_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0); + preedit_ = new InformationTextArea(gfx::ALIGN_LEFT, kMinPreeditAreaWidth); + candidate_area_ = new views::View; + auxiliary_text_->SetVisible(false); + preedit_->SetVisible(false); + candidate_area_->SetVisible(false); + preedit_->SetBorder(InformationTextArea::BOTTOM); + if (candidate_window_.orientation() == ui::CandidateWindow::VERTICAL) { + AddChildView(preedit_); + AddChildView(candidate_area_); + AddChildView(auxiliary_text_); + auxiliary_text_->SetBorder(InformationTextArea::TOP); + candidate_area_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kVertical, 0, 0, 0)); + } else { + AddChildView(preedit_); + AddChildView(auxiliary_text_); + AddChildView(candidate_area_); + auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT); + auxiliary_text_->SetBorder(InformationTextArea::BOTTOM); + candidate_area_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, 0, 0, 0)); + } +} + +CandidateWindowView::~CandidateWindowView() { +} + +views::Widget* CandidateWindowView::InitWidget() { + views::Widget* widget = BubbleDelegateView::CreateBubble(this); + + views::corewm::SetWindowVisibilityAnimationType( + widget->GetNativeView(), + views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); + + GetBubbleFrameView()->SetBubbleBorder( + new CandidateWindowBorder(parent_window())); + return widget; +} + +void CandidateWindowView::UpdateVisibility() { + if (candidate_area_->visible() || auxiliary_text_->visible() || + preedit_->visible()) { + SizeToContents(); + } else { + GetWidget()->Close(); + } +} + +void CandidateWindowView::HideLookupTable() { + candidate_area_->SetVisible(false); + auxiliary_text_->SetVisible(false); + UpdateVisibility(); +} + +void CandidateWindowView::HidePreeditText() { + preedit_->SetVisible(false); + UpdateVisibility(); +} + +void CandidateWindowView::ShowPreeditText() { + preedit_->SetVisible(true); + UpdateVisibility(); +} + +void CandidateWindowView::UpdatePreeditText(const std::string& utf8_text) { + preedit_->SetText(utf8_text); +} + +void CandidateWindowView::ShowLookupTable() { + candidate_area_->SetVisible(true); + auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible()); + UpdateVisibility(); +} + +void CandidateWindowView::UpdateCandidates( + const ui::CandidateWindow& new_candidate_window) { + // Updating the candidate views is expensive. We'll skip this if possible. + if (!candidate_window_.IsEqual(new_candidate_window)) { + if (candidate_window_.orientation() != new_candidate_window.orientation()) { + // If the new layout is vertical, the aux text should appear at the + // bottom. If horizontal, it should appear between preedit and candidates. + if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { + ReorderChildView(auxiliary_text_, -1); + auxiliary_text_->SetAlignment(gfx::ALIGN_RIGHT); + auxiliary_text_->SetBorder(InformationTextArea::TOP); + candidate_area_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kVertical, 0, 0, 0)); + } else { + ReorderChildView(auxiliary_text_, 1); + auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT); + auxiliary_text_->SetBorder(InformationTextArea::BOTTOM); + candidate_area_->SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, 0, 0, 0)); + } + } + + // Initialize candidate views if necessary. + MaybeInitializeCandidateViews(new_candidate_window); + + should_show_at_composition_head_ + = new_candidate_window.show_window_at_composition(); + // Compute the index of the current page. + const int current_page_index = ComputePageIndex(new_candidate_window); + if (current_page_index < 0) + return; + + // Update the candidates in the current page. + const size_t start_from = + current_page_index * new_candidate_window.page_size(); + + int max_shortcut_width = 0; + int max_candidate_width = 0; + for (size_t i = 0; i < candidate_views_.size(); ++i) { + const size_t index_in_page = i; + const size_t candidate_index = start_from + index_in_page; + CandidateView* candidate_view = candidate_views_[index_in_page]; + // Set the candidate text. + if (candidate_index < new_candidate_window.candidates().size()) { + const ui::CandidateWindow::Entry& entry = + new_candidate_window.candidates()[candidate_index]; + candidate_view->SetEntry(entry); + candidate_view->SetState(views::Button::STATE_NORMAL); + candidate_view->SetInfolistIcon(!entry.description_title.empty()); + } else { + // Disable the empty row. + candidate_view->SetEntry(ui::CandidateWindow::Entry()); + candidate_view->SetState(views::Button::STATE_DISABLED); + candidate_view->SetInfolistIcon(false); + } + if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { + int shortcut_width = 0; + int candidate_width = 0; + candidate_views_[i]->GetPreferredWidths( + &shortcut_width, &candidate_width); + max_shortcut_width = std::max(max_shortcut_width, shortcut_width); + max_candidate_width = std::max(max_candidate_width, candidate_width); + } + } + if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { + for (size_t i = 0; i < candidate_views_.size(); ++i) + candidate_views_[i]->SetWidths(max_shortcut_width, max_candidate_width); + } + + CandidateWindowBorder* border = static_cast( + GetBubbleFrameView()->bubble_border()); + if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) + border->set_offset(max_shortcut_width); + else + border->set_offset(0); + } + // Update the current candidate window. We'll use candidate_window_ from here. + // Note that SelectCandidateAt() uses candidate_window_. + candidate_window_.CopyFrom(new_candidate_window); + + // Select the current candidate in the page. + if (candidate_window_.is_cursor_visible()) { + if (candidate_window_.page_size()) { + const int current_candidate_in_page = + candidate_window_.cursor_position() % candidate_window_.page_size(); + SelectCandidateAt(current_candidate_in_page); + } + } else { + // Unselect the currently selected candidate. + if (0 <= selected_candidate_index_in_page_ && + static_cast(selected_candidate_index_in_page_) < + candidate_views_.size()) { + candidate_views_[selected_candidate_index_in_page_]->SetState( + views::Button::STATE_NORMAL); + selected_candidate_index_in_page_ = -1; + } + } + + // Updates auxiliary text + auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible()); + auxiliary_text_->SetText(candidate_window_.auxiliary_text()); +} + +void CandidateWindowView::SetCursorBounds(const gfx::Rect& cursor_bounds, + const gfx::Rect& composition_head) { + if (candidate_window_.show_window_at_composition()) + SetAnchorRect(composition_head); + else + SetAnchorRect(cursor_bounds); +} + +void CandidateWindowView::MaybeInitializeCandidateViews( + const ui::CandidateWindow& candidate_window) { + const ui::CandidateWindow::Orientation orientation = + candidate_window.orientation(); + const size_t page_size = candidate_window.page_size(); + + // Reset all candidate_views_ when orientation changes. + if (orientation != candidate_window_.orientation()) + STLDeleteElements(&candidate_views_); + + while (page_size < candidate_views_.size()) { + delete candidate_views_.back(); + candidate_views_.pop_back(); + } + while (page_size > candidate_views_.size()) { + CandidateView* new_candidate = new CandidateView(this, orientation); + candidate_area_->AddChildView(new_candidate); + candidate_views_.push_back(new_candidate); + } +} + +void CandidateWindowView::SelectCandidateAt(int index_in_page) { + const int current_page_index = ComputePageIndex(candidate_window_); + if (current_page_index < 0) { + return; + } + + const int cursor_absolute_index = + candidate_window_.page_size() * current_page_index + index_in_page; + // Ignore click on out of range views. + if (cursor_absolute_index < 0 || + candidate_window_.candidates().size() <= + static_cast(cursor_absolute_index)) { + return; + } + + // Remember the currently selected candidate index in the current page. + selected_candidate_index_in_page_ = index_in_page; + + // Select the candidate specified by index_in_page. + candidate_views_[index_in_page]->SetState(views::Button::STATE_PRESSED); + + // Update the cursor indexes in the model. + candidate_window_.set_cursor_position(cursor_absolute_index); +} + +void CandidateWindowView::ButtonPressed(views::Button* sender, + const ui::Event& event) { + for (size_t i = 0; i < candidate_views_.size(); ++i) { + if (sender == candidate_views_[i]) { + FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i)); + return; + } + } +} + +} // namespace ime +} // namespace ash diff --git a/ash/ime/candidate_window_view.h b/ash/ime/candidate_window_view.h new file mode 100644 index 0000000..34bdcf3 --- /dev/null +++ b/ash/ime/candidate_window_view.h @@ -0,0 +1,134 @@ +// Copyright 2014 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. + +#ifndef ASH_IME_CANDIDATE_WINDOW_VIEW_H_ +#define ASH_IME_CANDIDATE_WINDOW_VIEW_H_ + +#include "ash/ash_export.h" +#include "ui/base/ime/candidate_window.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/controls/button/button.h" + +namespace ash { +namespace ime { + +class CandidateView; +class InformationTextArea; + +// CandidateWindowView is the main container of the candidate window UI. +class ASH_EXPORT CandidateWindowView : public views::BubbleDelegateView, + public views::ButtonListener { + public: + // The object can be monitored by the observer. + class Observer { + public: + virtual ~Observer() {} + // The function is called when a candidate is committed. + virtual void OnCandidateCommitted(int index) = 0; + }; + + explicit CandidateWindowView(gfx::NativeView parent); + virtual ~CandidateWindowView(); + views::Widget* InitWidget(); + + // Adds the given observer. The ownership is not transferred. + void AddObserver(Observer* observer) { + observers_.AddObserver(observer); + } + + // Removes the given observer. + void RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); + } + + // Hides the lookup table. + void HideLookupTable(); + + // Hides the auxiliary text. + void HideAuxiliaryText(); + + // Hides the preedit text. + void HidePreeditText(); + + // Shows the lookup table. + void ShowLookupTable(); + + // Shows the auxiliary text. + void ShowAuxiliaryText(); + + // Shows the preedit text. + void ShowPreeditText(); + + // Updates the preedit text. + void UpdatePreeditText(const std::string& utf8_text); + + // Updates candidates of the candidate window from |candidate_window|. + // Candidates are arranged per |orientation|. + void UpdateCandidates(const ui::CandidateWindow& candidate_window); + + void SetCursorBounds(const gfx::Rect& cursor_bounds, + const gfx::Rect& composition_head); + + private: + friend class CandidateWindowViewTest; + + // Overridden from views::ButtonListener: + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; + + void SelectCandidateAt(int index_in_page); + void UpdateVisibility(); + + // Initializes the candidate views if needed. + void MaybeInitializeCandidateViews( + const ui::CandidateWindow& candidate_window); + + // The candidate window data model. + ui::CandidateWindow candidate_window_; + + // The index in the current page of the candidate currently being selected. + int selected_candidate_index_in_page_; + + // The observers of the object. + ObserverList observers_; + + // Views created in the class will be part of tree of |this|, so these + // child views will be deleted when |this| is deleted. + InformationTextArea* auxiliary_text_; + InformationTextArea* preedit_; + views::View* candidate_area_; + + // The candidate views are used for rendering candidates. + std::vector candidate_views_; + + // Current columns size in |candidate_area_|. + gfx::Size previous_shortcut_column_size_; + gfx::Size previous_candidate_column_size_; + gfx::Size previous_annotation_column_size_; + + // The last cursor bounds. + gfx::Rect cursor_bounds_; + + // The last compostion head bounds. + gfx::Rect composition_head_bounds_; + + // True if the candidate window should be shown with aligning with composition + // text as opposed to the cursor. + bool should_show_at_composition_head_; + + // True if the candidate window should be shonw on the upper side of + // composition text. + bool should_show_upper_side_; + + // True if the candidate window was open. This is used to determine when to + // send OnCandidateWindowOpened and OnCandidateWindowClosed events. + bool was_candidate_window_open_; + + DISALLOW_COPY_AND_ASSIGN(CandidateWindowView); +}; + +} // namespace ime +} // namespace ash + +#endif // ASH_IME_CANDIDATE_WINDOW_VIEW_H_ diff --git a/ash/ime/candidate_window_view_unittest.cc b/ash/ime/candidate_window_view_unittest.cc new file mode 100644 index 0000000..45ab518 --- /dev/null +++ b/ash/ime/candidate_window_view_unittest.cc @@ -0,0 +1,384 @@ +// Copyright 2014 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 "ash/ime/candidate_window_view.h" + +#include + +#include "ash/ime/candidate_view.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace ime { + +namespace { +const char* kSampleCandidate[] = { + "Sample Candidate 1", + "Sample Candidate 2", + "Sample Candidate 3" +}; +const char* kSampleAnnotation[] = { + "Sample Annotation 1", + "Sample Annotation 2", + "Sample Annotation 3" +}; +const char* kSampleDescriptionTitle[] = { + "Sample Description Title 1", + "Sample Description Title 2", + "Sample Description Title 3", +}; +const char* kSampleDescriptionBody[] = { + "Sample Description Body 1", + "Sample Description Body 2", + "Sample Description Body 3", +}; + +void InitCandidateWindow(size_t page_size, + ui::CandidateWindow* candidate_window) { + candidate_window->set_cursor_position(0); + candidate_window->set_page_size(page_size); + candidate_window->mutable_candidates()->clear(); + candidate_window->set_orientation(ui::CandidateWindow::VERTICAL); +} + +void InitCandidateWindowWithCandidatesFilled( + size_t page_size, + ui::CandidateWindow* candidate_window) { + InitCandidateWindow(page_size, candidate_window); + for (size_t i = 0; i < page_size; ++i) { + ui::CandidateWindow::Entry entry; + entry.value = base::StringPrintf("value %lld", + static_cast(i)); + entry.label = base::StringPrintf("%lld", + static_cast(i)); + candidate_window->mutable_candidates()->push_back(entry); + } +} + +} // namespace + +class CandidateWindowViewTest : public views::ViewsTestBase { + public: + CandidateWindowViewTest() {} + virtual ~CandidateWindowViewTest() {} + + protected: + virtual void SetUp() { + views::ViewsTestBase::SetUp(); + candidate_window_view_ = new CandidateWindowView(GetContext()); + candidate_window_view_->InitWidget(); + } + + CandidateWindowView* candidate_window_view() { + return candidate_window_view_; + } + + int selected_candidate_index_in_page() { + return candidate_window_view_->selected_candidate_index_in_page_; + } + + size_t GetCandidatesSize() const { + return candidate_window_view_->candidate_views_.size(); + } + + CandidateView* GetCandidateAt(size_t i) { + return candidate_window_view_->candidate_views_[i]; + } + + void SelectCandidateAt(int index_in_page) { + candidate_window_view_->SelectCandidateAt(index_in_page); + } + + void MaybeInitializeCandidateViews( + const ui::CandidateWindow& candidate_window) { + candidate_window_view_->MaybeInitializeCandidateViews(candidate_window); + } + + void ExpectLabels(const std::string& shortcut, + const std::string& candidate, + const std::string& annotation, + const CandidateView* row) { + EXPECT_EQ(shortcut, base::UTF16ToUTF8(row->shortcut_label_->text())); + EXPECT_EQ(candidate, base::UTF16ToUTF8(row->candidate_label_->text())); + EXPECT_EQ(annotation, base::UTF16ToUTF8(row->annotation_label_->text())); + } + + private: + // owned by |parent_|. + CandidateWindowView* candidate_window_view_; + + DISALLOW_COPY_AND_ASSIGN(CandidateWindowViewTest); +}; + +TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility) { + // Visible (by default) cursor. + ui::CandidateWindow candidate_window; + const int candidate_window_size = 9; + InitCandidateWindowWithCandidatesFilled(candidate_window_size, + &candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(0, selected_candidate_index_in_page()); + + // Invisible cursor. + candidate_window.set_is_cursor_visible(false); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(-1, selected_candidate_index_in_page()); + + // Move the cursor to the end. + candidate_window.set_cursor_position(candidate_window_size - 1); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(-1, selected_candidate_index_in_page()); + + // Change the cursor to visible. The cursor must be at the end. + candidate_window.set_is_cursor_visible(true); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(candidate_window_size - 1, selected_candidate_index_in_page()); +} + +TEST_F(CandidateWindowViewTest, SelectCandidateAtTest) { + // Set 9 candidates. + ui::CandidateWindow candidate_window_large; + const int candidate_window_large_size = 9; + InitCandidateWindowWithCandidatesFilled(candidate_window_large_size, + &candidate_window_large); + candidate_window_large.set_cursor_position(candidate_window_large_size - 1); + candidate_window_view()->UpdateCandidates(candidate_window_large); + + // Select the last candidate. + SelectCandidateAt(candidate_window_large_size - 1); + + // Reduce the number of candidates to 3. + ui::CandidateWindow candidate_window_small; + const int candidate_window_small_size = 3; + InitCandidateWindowWithCandidatesFilled(candidate_window_small_size, + &candidate_window_small); + candidate_window_small.set_cursor_position(candidate_window_small_size - 1); + // Make sure the test doesn't crash if the candidate window reduced + // its size. (crbug.com/174163) + candidate_window_view()->UpdateCandidates(candidate_window_small); + SelectCandidateAt(candidate_window_small_size - 1); +} + +TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { + const char* kEmptyLabel = ""; + const char* kCustomizedLabel[] = { "a", "s", "d" }; + const char* kExpectedHorizontalCustomizedLabel[] = { "a.", "s.", "d." }; + + { + SCOPED_TRACE("candidate_views allocation test"); + const size_t kMaxPageSize = 16; + for (size_t i = 1; i < kMaxPageSize; ++i) { + ui::CandidateWindow candidate_window; + InitCandidateWindow(i, &candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(i, GetCandidatesSize()); + } + } + { + SCOPED_TRACE("Empty string for each labels expects empty labels(vertical)"); + const size_t kPageSize = 3; + ui::CandidateWindow candidate_window; + InitCandidateWindow(kPageSize, &candidate_window); + + candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); + for (size_t i = 0; i < kPageSize; ++i) { + ui::CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + entry.annotation = kSampleAnnotation[i]; + entry.description_title = kSampleDescriptionTitle[i]; + entry.description_body = kSampleDescriptionBody[i]; + entry.label = kEmptyLabel; + candidate_window.mutable_candidates()->push_back(entry); + } + + candidate_window_view()->UpdateCandidates(candidate_window); + + ASSERT_EQ(kPageSize, GetCandidatesSize()); + for (size_t i = 0; i < kPageSize; ++i) { + ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], + GetCandidateAt(i)); + } + } + { + SCOPED_TRACE( + "Empty string for each labels expect empty labels(horizontal)"); + const size_t kPageSize = 3; + ui::CandidateWindow candidate_window; + InitCandidateWindow(kPageSize, &candidate_window); + + candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL); + for (size_t i = 0; i < kPageSize; ++i) { + ui::CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + entry.annotation = kSampleAnnotation[i]; + entry.description_title = kSampleDescriptionTitle[i]; + entry.description_body = kSampleDescriptionBody[i]; + entry.label = kEmptyLabel; + candidate_window.mutable_candidates()->push_back(entry); + } + + candidate_window_view()->UpdateCandidates(candidate_window); + + ASSERT_EQ(kPageSize, GetCandidatesSize()); + // Confirm actual labels not containing ".". + for (size_t i = 0; i < kPageSize; ++i) { + ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], + GetCandidateAt(i)); + } + } + { + SCOPED_TRACE("Vertical customized label case"); + const size_t kPageSize = 3; + ui::CandidateWindow candidate_window; + InitCandidateWindow(kPageSize, &candidate_window); + + candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); + for (size_t i = 0; i < kPageSize; ++i) { + ui::CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + entry.annotation = kSampleAnnotation[i]; + entry.description_title = kSampleDescriptionTitle[i]; + entry.description_body = kSampleDescriptionBody[i]; + entry.label = kCustomizedLabel[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + + candidate_window_view()->UpdateCandidates(candidate_window); + + ASSERT_EQ(kPageSize, GetCandidatesSize()); + // Confirm actual labels not containing ".". + for (size_t i = 0; i < kPageSize; ++i) { + ExpectLabels(kCustomizedLabel[i], + kSampleCandidate[i], + kSampleAnnotation[i], + GetCandidateAt(i)); + } + } + { + SCOPED_TRACE("Horizontal customized label case"); + const size_t kPageSize = 3; + ui::CandidateWindow candidate_window; + InitCandidateWindow(kPageSize, &candidate_window); + + candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL); + for (size_t i = 0; i < kPageSize; ++i) { + ui::CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + entry.annotation = kSampleAnnotation[i]; + entry.description_title = kSampleDescriptionTitle[i]; + entry.description_body = kSampleDescriptionBody[i]; + entry.label = kCustomizedLabel[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + + candidate_window_view()->UpdateCandidates(candidate_window); + + ASSERT_EQ(kPageSize, GetCandidatesSize()); + // Confirm actual labels not containing ".". + for (size_t i = 0; i < kPageSize; ++i) { + ExpectLabels(kExpectedHorizontalCustomizedLabel[i], + kSampleCandidate[i], + kSampleAnnotation[i], + GetCandidateAt(i)); + } + } +} + +TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { + const size_t kPageSize = 10; + ui::CandidateWindow candidate_window; + ui::CandidateWindow no_shortcut_candidate_window; + + const char kSampleCandidate1[] = "Sample String 1"; + const char kSampleCandidate2[] = "\xE3\x81\x82"; // multi byte string. + const char kSampleCandidate3[] = "....."; + + const char kSampleShortcut1[] = "1"; + const char kSampleShortcut2[] = "b"; + const char kSampleShortcut3[] = "C"; + + const char kSampleAnnotation1[] = "Sample Annotation 1"; + const char kSampleAnnotation2[] = "\xE3\x81\x82"; // multi byte string. + const char kSampleAnnotation3[] = "......"; + + // Create CandidateWindow object. + InitCandidateWindow(kPageSize, &candidate_window); + + candidate_window.set_cursor_position(0); + candidate_window.set_page_size(3); + candidate_window.mutable_candidates()->clear(); + candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); + no_shortcut_candidate_window.CopyFrom(candidate_window); + + ui::CandidateWindow::Entry entry; + entry.value = kSampleCandidate1; + entry.annotation = kSampleAnnotation1; + candidate_window.mutable_candidates()->push_back(entry); + entry.label = kSampleShortcut1; + no_shortcut_candidate_window.mutable_candidates()->push_back(entry); + + entry.value = kSampleCandidate2; + entry.annotation = kSampleAnnotation2; + candidate_window.mutable_candidates()->push_back(entry); + entry.label = kSampleShortcut2; + no_shortcut_candidate_window.mutable_candidates()->push_back(entry); + + entry.value = kSampleCandidate3; + entry.annotation = kSampleAnnotation3; + candidate_window.mutable_candidates()->push_back(entry); + entry.label = kSampleShortcut3; + no_shortcut_candidate_window.mutable_candidates()->push_back(entry); + + int before_height = 0; + + // Test for shortcut mode to no-shortcut mode. + // Initialize with a shortcut mode candidate window. + MaybeInitializeCandidateViews(candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); + // Check the selected index is invalidated. + EXPECT_EQ(-1, selected_candidate_index_in_page()); + before_height = + GetCandidateAt(0)->GetContentsBounds().height(); + // Checks all entry have same row height. + for (size_t i = 1; i < GetCandidatesSize(); ++i) + EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); + + // Initialize with a no shortcut mode candidate window. + MaybeInitializeCandidateViews(no_shortcut_candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); + // Check the selected index is invalidated. + EXPECT_EQ(-1, selected_candidate_index_in_page()); + EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height()); + // Checks all entry have same row height. + for (size_t i = 1; i < GetCandidatesSize(); ++i) + EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); + + // Test for no-shortcut mode to shortcut mode. + // Initialize with a no shortcut mode candidate window. + MaybeInitializeCandidateViews(no_shortcut_candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); + // Check the selected index is invalidated. + EXPECT_EQ(-1, selected_candidate_index_in_page()); + before_height = GetCandidateAt(0)->GetContentsBounds().height(); + // Checks all entry have same row height. + for (size_t i = 1; i < GetCandidatesSize(); ++i) + EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); + + // Initialize with a shortcut mode candidate window. + MaybeInitializeCandidateViews(candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); + // Check the selected index is invalidated. + EXPECT_EQ(-1, selected_candidate_index_in_page()); + EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height()); + // Checks all entry have same row height. + for (size_t i = 1; i < GetCandidatesSize(); ++i) + EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); +} + +} // namespace ime +} // namespace ash diff --git a/ash/ime/infolist_window.cc b/ash/ime/infolist_window.cc new file mode 100644 index 0000000..d72eed3 --- /dev/null +++ b/ash/ime/infolist_window.cc @@ -0,0 +1,282 @@ +// Copyright 2014 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 "ash/ime/infolist_window.h" + +#include +#include + +#include "ash/ime/candidate_window_constants.h" +#include "base/logging.h" +#include "grit/ash_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/font.h" +#include "ui/native_theme/native_theme.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/bubble/bubble_border.h" +#include "ui/views/bubble/bubble_frame_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/corewm/window_animations.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace ime { + +namespace { +// The width of an info-list. +const int kInfolistEntryWidth = 200; + +// The milliseconds of the delay to show the infolist window. +const int kInfolistShowDelayMilliSeconds = 500; +// The milliseconds of the delay to hide the infolist window. +const int kInfolistHideDelayMilliSeconds = 500; + +/////////////////////////////////////////////////////////////////////////////// +// InfolistBorder +// The BubbleBorder subclass to draw the border and determine its position. +class InfolistBorder : public views::BubbleBorder { + public: + InfolistBorder(); + virtual ~InfolistBorder(); + + // views::BubbleBorder implementation. + virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, + const gfx::Size& contents_size) const OVERRIDE; + virtual gfx::Insets GetInsets() const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(InfolistBorder); +}; + +InfolistBorder::InfolistBorder() + : views::BubbleBorder(views::BubbleBorder::LEFT_CENTER, + views::BubbleBorder::NO_SHADOW, + SK_ColorTRANSPARENT) { + set_paint_arrow(views::BubbleBorder::PAINT_NONE); +} + +InfolistBorder::~InfolistBorder() {} + +gfx::Rect InfolistBorder::GetBounds(const gfx::Rect& anchor_rect, + const gfx::Size& contents_size) const { + gfx::Rect bounds(contents_size); + bounds.set_x(is_arrow_on_left(arrow()) ? + anchor_rect.right() : anchor_rect.x() - contents_size.width()); + // InfolistBorder modifies the vertical position based on the arrow offset + // although it doesn't draw the arrow. The arrow offset is the half of + // |contents_size| by default but can be modified through the off-screen logic + // in BubbleFrameView. + bounds.set_y(anchor_rect.y() + contents_size.height() / 2 - + GetArrowOffset(contents_size)); + return bounds; +} + +gfx::Insets InfolistBorder::GetInsets() const { + // This has to be specified and return empty insets to place the infolist + // window without the gap. + return gfx::Insets(); +} + +} // namespace + +// InfolistRow renderes a row of a infolist. +class InfolistEntryView : public views::View { + public: + InfolistEntryView(const ui::InfolistEntry& entry, + const gfx::FontList& title_font, + const gfx::FontList& description_font); + virtual ~InfolistEntryView(); + + void SetEntry(const ui::InfolistEntry& entry); + + private: + // views::View implementation. + virtual gfx::Size GetPreferredSize() OVERRIDE; + + void UpdateBackground(); + + ui::InfolistEntry entry_; + + // The title label. Owned by views hierarchy. + views::Label* title_label_; + + // The description label. Owned by views hierarchy. + views::Label* description_label_; + + DISALLOW_COPY_AND_ASSIGN(InfolistEntryView); +}; + +InfolistEntryView::InfolistEntryView(const ui::InfolistEntry& entry, + const gfx::FontList& title_font, + const gfx::FontList& description_font) + : entry_(entry) { + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + + title_label_ = new views::Label(entry.title); + title_label_->SetPosition(gfx::Point(0, 0)); + title_label_->SetFontList(title_font); + title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + title_label_->set_border( + views::Border::CreateEmptyBorder(4, 7, 2, 4)); + + description_label_ = new views::Label(entry.body); + description_label_->SetPosition(gfx::Point(0, 0)); + description_label_->SetFontList(description_font); + description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + description_label_->SetMultiLine(true); + description_label_->SizeToFit(kInfolistEntryWidth); + description_label_->set_border( + views::Border::CreateEmptyBorder(2, 17, 4, 4)); + AddChildView(title_label_); + AddChildView(description_label_); + UpdateBackground(); +} + +InfolistEntryView::~InfolistEntryView() {} + +void InfolistEntryView::SetEntry(const ui::InfolistEntry& entry) { + if (entry_ == entry) + return; + + entry_ = entry; + title_label_->SetText(entry_.title); + description_label_->SetText(entry_.body); + UpdateBackground(); +} + +gfx::Size InfolistEntryView::GetPreferredSize() { + return gfx::Size(kInfolistEntryWidth, GetHeightForWidth(kInfolistEntryWidth)); +} + +void InfolistEntryView::UpdateBackground() { + if (entry_.highlighted) { + set_background( + views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused))); + set_border( + views::Border::CreateSolidBorder(1, GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_FocusedBorderColor))); + } else { + set_background(NULL); + set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); + } + SchedulePaint(); +} + +/////////////////////////////////////////////////////////////////////////////// +// InfolistWindow + +InfolistWindow::InfolistWindow(views::View* candidate_window, + const std::vector& entries) + : views::BubbleDelegateView(candidate_window, views::BubbleBorder::NONE), + title_font_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 15)), + description_font_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 11)) { + set_move_with_anchor(true); + set_margins(gfx::Insets()); + + set_background( + views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_WindowBackground))); + set_border( + views::Border::CreateSolidBorder(1, GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_MenuBorderColor))); + + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); + + views::Label* caption_label = new views::Label( + l10n_util::GetStringUTF16(IDS_ASH_IME_INFOLIST_WINDOW_TITLE)); + caption_label->SetFontList( + caption_label->font_list().DeriveFontList(kFontSizeDelta - 2)); + caption_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + caption_label->SetEnabledColor(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_LabelEnabledColor)); + caption_label->set_border(views::Border::CreateEmptyBorder(2, 2, 2, 2)); + caption_label->set_background(views::Background::CreateSolidBackground( + color_utils::AlphaBlend(SK_ColorBLACK, + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_WindowBackground), + 0x10))); + + AddChildView(caption_label); + + for (size_t i = 0; i < entries.size(); ++i) { + entry_views_.push_back( + new InfolistEntryView(entries[i], title_font_, description_font_)); + AddChildView(entry_views_.back()); + } +} + +InfolistWindow::~InfolistWindow() { +} + +void InfolistWindow::InitWidget() { + views::Widget* widget = views::BubbleDelegateView::CreateBubble(this); + views::corewm::SetWindowVisibilityAnimationType( + widget->GetNativeView(), + views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); + + // BubbleFrameView will be initialized through CreateBubble. + GetBubbleFrameView()->SetBubbleBorder(new InfolistBorder()); + SizeToContents(); +} + +void InfolistWindow::Relayout(const std::vector& entries) { + size_t i = 0; + for (; i < entries.size(); ++i) { + if (i < entry_views_.size()) { + entry_views_[i]->SetEntry(entries[i]); + } else { + InfolistEntryView* new_entry = new InfolistEntryView( + entries[i], title_font_, description_font_); + AddChildView(new_entry); + entry_views_.push_back(new_entry); + } + } + + if (i < entry_views_.size()) { + for (; i < entry_views_.size(); ++i) + delete entry_views_[i]; + entry_views_.resize(entries.size()); + } + + Layout(); + GetBubbleFrameView()->bubble_border()->set_arrow_offset(0); + SizeToContents(); +} + +void InfolistWindow::ShowWithDelay() { + show_hide_timer_.Start( + FROM_HERE, + base::TimeDelta::FromMilliseconds(kInfolistShowDelayMilliSeconds), + GetWidget(), + &views::Widget::Show); +} + +void InfolistWindow::HideWithDelay() { + show_hide_timer_.Start( + FROM_HERE, + base::TimeDelta::FromMilliseconds(kInfolistHideDelayMilliSeconds), + GetWidget(), + &views::Widget::Close); +} + +void InfolistWindow::ShowImmediately() { + show_hide_timer_.Stop(); + GetWidget()->Show(); +} + +void InfolistWindow::HideImmediately() { + show_hide_timer_.Stop(); + GetWidget()->Close(); +} + +void InfolistWindow::WindowClosing() { + show_hide_timer_.Stop(); +} + +} // namespace ime +} // namespace ash diff --git a/ash/ime/infolist_window.h b/ash/ime/infolist_window.h new file mode 100644 index 0000000..e7c984a --- /dev/null +++ b/ash/ime/infolist_window.h @@ -0,0 +1,64 @@ +// Copyright 2014 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. + +#ifndef ASH_IME_INFOLIST_WINDOW_H_ +#define ASH_IME_INFOLIST_WINDOW_H_ + +#include +#include + +#include "ash/ash_export.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/timer/timer.h" +#include "ui/base/ime/infolist_entry.h" +#include "ui/gfx/font_list.h" +#include "ui/views/bubble/bubble_delegate.h" + +namespace ash { +namespace ime { + +class InfolistEntryView; + +// A widget delegate representing the infolist window UI. +class ASH_EXPORT InfolistWindow : public views::BubbleDelegateView { + public: + InfolistWindow(views::View* candidate_window, + const std::vector& entries); + virtual ~InfolistWindow(); + void InitWidget(); + + // Updates infolist contents with |entries|. + void Relayout(const std::vector& entries); + + // Show/hide itself with a delay. + void ShowWithDelay(); + void HideWithDelay(); + + // Show/hide without delays. + void ShowImmediately(); + void HideImmediately(); + + private: + // views::WidgetDelegate implementation. + virtual void WindowClosing() OVERRIDE; + + // The list of visible entries. Owned by views hierarchy. + std::vector entry_views_; + + // Information title font. + gfx::FontList title_font_; + + // Information description font. + gfx::FontList description_font_; + + base::OneShotTimer show_hide_timer_; + + DISALLOW_COPY_AND_ASSIGN(InfolistWindow); +}; + +} // namespace ime +} // namespace ash + +#endif // ASH_IME_INFOLIST_WINDOW_H_ diff --git a/ash/ime/mode_indicator_view.cc b/ash/ime/mode_indicator_view.cc new file mode 100644 index 0000000..64c9b24 --- /dev/null +++ b/ash/ime/mode_indicator_view.cc @@ -0,0 +1,69 @@ +// Copyright 2014 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 "ash/ime/mode_indicator_view.h" + +#include "base/logging.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/controls/label.h" +#include "ui/views/corewm/window_animations.h" +#include "ui/views/layout/fill_layout.h" + +namespace ash { +namespace ime { + +namespace { +// Minimum size of inner contents in pixel. +// 43 is the designed size including the default margin (6 * 2). +const int kMinSize = 31; + +// After this duration in msec, the mode inicator will be fading out. +const int kShowingDuration = 500; +} // namespace + + +ModeIndicatorView::ModeIndicatorView(gfx::NativeView parent, + const gfx::Rect& cursor_bounds, + const base::string16& label) + : cursor_bounds_(cursor_bounds), + label_view_(new views::Label(label)) { + set_use_focusless(true); + set_accept_events(false); + set_parent_window(parent); + set_shadow(views::BubbleBorder::NO_SHADOW); + set_arrow(views::BubbleBorder::TOP_CENTER); +} + +ModeIndicatorView::~ModeIndicatorView() {} + +void ModeIndicatorView::FadeOut() { + StartFade(false); +} + +void ModeIndicatorView::ShowAndFadeOut() { + views::corewm::SetWindowVisibilityAnimationTransition( + GetWidget()->GetNativeView(), + views::corewm::ANIMATE_HIDE); + GetWidget()->Show(); + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kShowingDuration), + this, + &ModeIndicatorView::FadeOut); +} + +gfx::Size ModeIndicatorView::GetPreferredSize() { + gfx::Size size = label_view_->GetPreferredSize(); + size.SetToMax(gfx::Size(kMinSize, kMinSize)); + return size; +} + +void ModeIndicatorView::Init() { + SetLayoutManager(new views::FillLayout()); + AddChildView(label_view_); + + SetAnchorRect(cursor_bounds_); +} + +} // namespace ime +} // namespace ash diff --git a/ash/ime/mode_indicator_view.h b/ash/ime/mode_indicator_view.h new file mode 100644 index 0000000..2b3a8a8 --- /dev/null +++ b/ash/ime/mode_indicator_view.h @@ -0,0 +1,52 @@ +// Copyright 2014 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. + +#ifndef ASH_IME_MODE_INDICATOR_VIEW_H_ +#define ASH_IME_MODE_INDICATOR_VIEW_H_ + +#include "ash/ash_export.h" +#include "base/strings/string16.h" +#include "base/timer/timer.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "ui/views/bubble/bubble_delegate.h" + +namespace views { +class Label; +} // namespace views + +namespace ash { +namespace ime { + +class ASH_EXPORT ModeIndicatorView : public views::BubbleDelegateView { + public: + ModeIndicatorView(gfx::NativeView parent, + const gfx::Rect& cursor_bounds, + const base::string16& label); + virtual ~ModeIndicatorView(); + + // Show the mode indicator then hide with fading animation. + void ShowAndFadeOut(); + + // views::BubbleDelegateView override: + virtual gfx::Size GetPreferredSize() OVERRIDE; + + protected: + // views::BubbleDelegateView override: + virtual void Init() OVERRIDE; + + private: + // Hide the window with fading animation. This is called from + // ShowAndFadeOut. + void FadeOut(); + + gfx::Rect cursor_bounds_; + views::Label* label_view_; + base::OneShotTimer timer_; +}; + +} // namespace ime +} // namespace ash + +#endif // ASH_IME_MODE_INDICATOR_VIEW_H_ diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index 24faf6e..4752eeb 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp @@ -3488,9 +3488,6 @@ Battery full Mongolian keyboard - - Information - US Mystery keyboard diff --git a/chrome/browser/chromeos/input_method/candidate_view.cc b/chrome/browser/chromeos/input_method/candidate_view.cc deleted file mode 100644 index e04a41d..0000000 --- a/chrome/browser/chromeos/input_method/candidate_view.cc +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2014 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 "chrome/browser/chromeos/input_method/candidate_view.h" - -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/chromeos/input_method/candidate_window_constants.h" -#include "ui/base/ime/candidate_window.h" -#include "ui/gfx/color_utils.h" -#include "ui/native_theme/native_theme.h" -#include "ui/views/background.h" -#include "ui/views/border.h" -#include "ui/views/controls/label.h" -#include "ui/views/widget/widget.h" - -namespace chromeos { -namespace input_method { - -namespace { - -// VerticalCandidateLabel is used for rendering candidate text in -// the vertical candidate window. -class VerticalCandidateLabel : public views::Label { - public: - VerticalCandidateLabel() {} - - private: - virtual ~VerticalCandidateLabel() {} - - // Returns the preferred size, but guarantees that the width has at - // least kMinCandidateLabelWidth pixels. - virtual gfx::Size GetPreferredSize() OVERRIDE { - gfx::Size size = Label::GetPreferredSize(); - size.SetToMax(gfx::Size(kMinCandidateLabelWidth, 0)); - size.SetToMin(gfx::Size(kMaxCandidateLabelWidth, size.height())); - return size; - } - - DISALLOW_COPY_AND_ASSIGN(VerticalCandidateLabel); -}; - -// Creates the shortcut label, and returns it (never returns NULL). -// The label text is not set in this function. -views::Label* CreateShortcutLabel( - ui::CandidateWindow::Orientation orientation, - const ui::NativeTheme& theme) { - // Create the shortcut label. The label will be owned by - // |wrapped_shortcut_label|, hence it's deleted when - // |wrapped_shortcut_label| is deleted. - views::Label* shortcut_label = new views::Label; - - if (orientation == ui::CandidateWindow::VERTICAL) { - shortcut_label->SetFontList( - shortcut_label->font_list().DeriveFontListWithSizeDeltaAndStyle( - kFontSizeDelta, gfx::Font::BOLD)); - } else { - shortcut_label->SetFontList( - shortcut_label->font_list().DeriveFontListWithSizeDelta( - kFontSizeDelta)); - } - // TODO(satorux): Maybe we need to use language specific fonts for - // candidate_label, like Chinese font for Chinese input method? - shortcut_label->SetEnabledColor(theme.GetSystemColor( - ui::NativeTheme::kColorId_LabelEnabledColor)); - shortcut_label->SetDisabledColor(theme.GetSystemColor( - ui::NativeTheme::kColorId_LabelDisabledColor)); - - // Setup paddings. - const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6); - const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0); - const gfx::Insets insets = - (orientation == ui::CandidateWindow::VERTICAL ? - kVerticalShortcutLabelInsets : - kHorizontalShortcutLabelInsets); - shortcut_label->set_border(views::Border::CreateEmptyBorder( - insets.top(), insets.left(), insets.bottom(), insets.right())); - - // Add decoration based on the orientation. - if (orientation == ui::CandidateWindow::VERTICAL) { - // Set the background color. - SkColor blackish = color_utils::AlphaBlend( - SK_ColorBLACK, - theme.GetSystemColor(ui::NativeTheme::kColorId_WindowBackground), - 0x40); - SkColor transparent_blakish = color_utils::AlphaBlend( - SK_ColorTRANSPARENT, blackish, 0xE0); - shortcut_label->set_background( - views::Background::CreateSolidBackground(transparent_blakish)); - } - - return shortcut_label; -} - -// Creates the candidate label, and returns it (never returns NULL). -// The label text is not set in this function. -views::Label* CreateCandidateLabel( - ui::CandidateWindow::Orientation orientation) { - views::Label* candidate_label = NULL; - - // Create the candidate label. The label will be added to |this| as a - // child view, hence it's deleted when |this| is deleted. - if (orientation == ui::CandidateWindow::VERTICAL) { - candidate_label = new VerticalCandidateLabel; - } else { - candidate_label = new views::Label; - } - - // Change the font size. - candidate_label->SetFontList( - candidate_label->font_list().DeriveFontListWithSizeDelta(kFontSizeDelta)); - candidate_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - return candidate_label; -} - -// Creates the annotation label, and return it (never returns NULL). -// The label text is not set in this function. -views::Label* CreateAnnotationLabel( - ui::CandidateWindow::Orientation orientation, - const ui::NativeTheme& theme) { - // Create the annotation label. - views::Label* annotation_label = new views::Label; - - // Change the font size and color. - annotation_label->SetFontList( - annotation_label->font_list().DeriveFontListWithSizeDelta( - kFontSizeDelta)); - annotation_label->SetEnabledColor(theme.GetSystemColor( - ui::NativeTheme::kColorId_LabelDisabledColor)); - annotation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - return annotation_label; -} - -} // namespace - -CandidateView::CandidateView( - views::ButtonListener* listener, - ui::CandidateWindow::Orientation orientation) - : views::CustomButton(listener), - orientation_(orientation), - shortcut_label_(NULL), - candidate_label_(NULL), - annotation_label_(NULL), - infolist_icon_(NULL) { - set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); - - const ui::NativeTheme& theme = *GetNativeTheme(); - shortcut_label_ = CreateShortcutLabel(orientation, theme); - candidate_label_ = CreateCandidateLabel(orientation); - annotation_label_ = CreateAnnotationLabel(orientation, theme); - - AddChildView(shortcut_label_); - AddChildView(candidate_label_); - AddChildView(annotation_label_); - - if (orientation == ui::CandidateWindow::VERTICAL) { - infolist_icon_ = new views::View; - infolist_icon_->set_background( - views::Background::CreateSolidBackground(theme.GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor))); - AddChildView(infolist_icon_); - } -} - -void CandidateView::GetPreferredWidths(int* shortcut_width, - int* candidate_width) { - *shortcut_width = shortcut_label_->GetPreferredSize().width(); - *candidate_width = candidate_label_->GetPreferredSize().width(); -} - -void CandidateView::SetWidths(int shortcut_width, int candidate_width) { - shortcut_width_ = shortcut_width; - shortcut_label_->SetVisible(shortcut_width_ != 0); - candidate_width_ = candidate_width; -} - -void CandidateView::SetEntry(const ui::CandidateWindow::Entry& entry) { - std::string label = entry.label; - if (!label.empty() && orientation_ != ui::CandidateWindow::VERTICAL) - label += '.'; - shortcut_label_->SetText(base::UTF8ToUTF16(label)); - candidate_label_->SetText(base::UTF8ToUTF16(entry.value)); - annotation_label_->SetText(base::UTF8ToUTF16(entry.annotation)); -} - -void CandidateView::SetInfolistIcon(bool enable) { - if (infolist_icon_) - infolist_icon_->SetVisible(enable); - SchedulePaint(); -} - -void CandidateView::StateChanged() { - shortcut_label_->SetEnabled(state() != STATE_DISABLED); - if (state() == STATE_PRESSED) { - ui::NativeTheme* theme = GetNativeTheme(); - set_background( - views::Background::CreateSolidBackground(theme->GetSystemColor( - ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused))); - set_border(views::Border::CreateSolidBorder( - 1, theme->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor))); - - // Cancel currently focused one. - for (int i = 0; i < parent()->child_count(); ++i) { - CandidateView* view = - static_cast((parent()->child_at(i))); - if (view != this && view->state() == STATE_PRESSED) - view->SetState(STATE_NORMAL); - } - } else { - set_background(NULL); - set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); - } -} - -bool CandidateView::OnMouseDragged(const ui::MouseEvent& event) { - if (!HitTestPoint(event.location())) { - // Moves the drag target to the sibling view. - gfx::Point location_in_widget(event.location()); - ConvertPointToWidget(this, &location_in_widget); - for (int i = 0; i < parent()->child_count(); ++i) { - views::View* sibling = parent()->child_at(i); - if (sibling == this) - continue; - gfx::Point location_in_sibling(location_in_widget); - ConvertPointFromWidget(sibling, &location_in_sibling); - if (sibling->HitTestPoint(location_in_sibling)) { - GetWidget()->GetRootView()->SetMouseHandler(sibling); - return sibling->OnMouseDragged(event); - } - } - - return false; - } - - return views::CustomButton::OnMouseDragged(event); -} - -void CandidateView::Layout() { - const int padding_width = - orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6; - int x = 0; - shortcut_label_->SetBounds(x, 0, shortcut_width_, height()); - if (shortcut_width_ > 0) - x += shortcut_width_ + padding_width; - candidate_label_->SetBounds(x, 0, candidate_width_, height()); - x += candidate_width_ + padding_width; - - int right = bounds().right(); - if (infolist_icon_ && infolist_icon_->visible()) { - infolist_icon_->SetBounds( - right - kInfolistIndicatorIconWidth - kInfolistIndicatorIconPadding, - kInfolistIndicatorIconPadding, - kInfolistIndicatorIconWidth, - height() - kInfolistIndicatorIconPadding * 2); - right -= kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2; - } - annotation_label_->SetBounds(x, 0, right - x, height()); -} - -gfx::Size CandidateView::GetPreferredSize() { - const int padding_width = - orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6; - gfx::Size size; - if (shortcut_label_->visible()) { - size = shortcut_label_->GetPreferredSize(); - size.SetToMax(gfx::Size(shortcut_width_, 0)); - size.Enlarge(padding_width, 0); - } - gfx::Size candidate_size = candidate_label_->GetPreferredSize(); - candidate_size.SetToMax(gfx::Size(candidate_width_, 0)); - size.Enlarge(candidate_size.width() + padding_width, 0); - size.SetToMax(candidate_size); - if (annotation_label_->visible()) { - gfx::Size annotation_size = annotation_label_->GetPreferredSize(); - size.Enlarge(annotation_size.width() + padding_width, 0); - size.SetToMax(annotation_size); - } - - // Reserves the margin for infolist_icon even if it's not visible. - size.Enlarge( - kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2, 0); - return size; -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/candidate_view.h b/chrome/browser/chromeos/input_method/candidate_view.h deleted file mode 100644 index a0bce3e..0000000 --- a/chrome/browser/chromeos/input_method/candidate_view.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2012 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. - -#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_VIEW_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_VIEW_H_ - -#include "base/gtest_prod_util.h" -#include "ui/base/ime/candidate_window.h" -#include "ui/views/controls/button/custom_button.h" -#include "ui/views/controls/label.h" -#include "ui/views/view.h" - -namespace chromeos { -namespace input_method { - -// CandidateView renderes a row of a candidate. -class CandidateView : public views::CustomButton { - public: - CandidateView(views::ButtonListener* listener, - ui::CandidateWindow::Orientation orientation); - virtual ~CandidateView() {} - - void GetPreferredWidths(int* shortcut_width, - int* candidate_width); - - void SetWidths(int shortcut_width, - int candidate_width); - - void SetEntry(const ui::CandidateWindow::Entry& entry); - - // Sets infolist icon. - void SetInfolistIcon(bool enable); - - private: - friend class CandidateWindowViewTest; - FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, ShortcutSettingTest); - - // Overridden from views::CustomButton: - virtual void StateChanged() OVERRIDE; - - // Overridden from View: - virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE; - virtual void Layout() OVERRIDE; - virtual gfx::Size GetPreferredSize() OVERRIDE; - - // The orientation of the candidate view. - ui::CandidateWindow::Orientation orientation_; - - // Views created in the class will be part of tree of |this|, so these - // child views will be deleted when |this| is deleted. - - // The shortcut label renders shortcut numbers like 1, 2, and 3. - views::Label* shortcut_label_; - // The candidate label renders candidates. - views::Label* candidate_label_; - // The annotation label renders annotations. - views::Label* annotation_label_; - - int shortcut_width_; - int candidate_width_; - - // The infolist icon. - views::View* infolist_icon_; - - DISALLOW_COPY_AND_ASSIGN(CandidateView); -}; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_VIEW_H_ diff --git a/chrome/browser/chromeos/input_method/candidate_window_constants.h b/chrome/browser/chromeos/input_method/candidate_window_constants.h deleted file mode 100644 index 148ab65..0000000 --- a/chrome/browser/chromeos/input_method/candidate_window_constants.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2012 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. - -#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_CONSTANTS_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_CONSTANTS_H_ - -namespace chromeos { -namespace input_method { - -// We'll use a bigger font size, so Chinese characters are more readable -// in the candidate window. -const int kFontSizeDelta = 2; - -// Currently the infolist window only supports Japanese font. -#if defined(GOOGLE_CHROME_BUILD) -const char kJapaneseFontName[] = "MotoyaG04Gothic"; -#else -const char kJapaneseFontName[] = "IPAPGothic"; -#endif - -// The minimum width of candidate labels in the vertical candidate -// window. We use this value to prevent the candidate window from being -// too narrow when all candidates are short. -const int kMinCandidateLabelWidth = 100; -// The maximum width of candidate labels in the vertical candidate -// window. We use this value to prevent the candidate window from being -// too wide when one of candidates are long. -const int kMaxCandidateLabelWidth = 500; -// The minimum width of preedit area. We use this value to prevent the -// candidate window from being too narrow when candidate lists are not shown. -const int kMinPreeditAreaWidth = 134; - -// The width of the infolist indicator icon in the candidate window. -const int kInfolistIndicatorIconWidth = 4; -// The padding size of the infolist indicator icon in the candidate window. -const int kInfolistIndicatorIconPadding = 2; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_CONSTANTS_H_ diff --git a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc index f801f81..4cc9b2f 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc +++ b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc @@ -7,13 +7,11 @@ #include #include +#include "ash/ime/infolist_window.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" #include "ash/wm/window_util.h" #include "base/logging.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/chromeos/input_method/candidate_window_view.h" -#include "chrome/browser/chromeos/input_method/infolist_window.h" #include "chrome/browser/chromeos/input_method/mode_indicator_controller.h" #include "ui/gfx/screen.h" #include "ui/views/widget/widget.h" @@ -47,10 +45,11 @@ void CandidateWindowControllerImpl::InitCandidateWindowView() { return; aura::Window* active_window = ash::wm::GetActiveWindow(); - candidate_window_view_ = new CandidateWindowView(ash::Shell::GetContainer( - active_window ? - active_window->GetRootWindow() : ash::Shell::GetTargetRootWindow(), - ash::internal::kShellWindowId_InputMethodContainer)); + candidate_window_view_ = + new ash::ime::CandidateWindowView(ash::Shell::GetContainer( + active_window ? + active_window->GetRootWindow() : ash::Shell::GetTargetRootWindow(), + ash::internal::kShellWindowId_InputMethodContainer)); candidate_window_view_->AddObserver(this); candidate_window_view_->SetCursorBounds(cursor_bounds_, composition_head_); views::Widget* widget = candidate_window_view_->InitWidget(); @@ -99,35 +98,6 @@ void CandidateWindowControllerImpl::FocusStateChanged(bool is_focused) { mode_indicator_controller_->FocusStateChanged(is_focused); } -// static -void CandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry( - const ui::CandidateWindow& candidate_window, - std::vector* infolist_entries, - bool* has_highlighted) { - DCHECK(infolist_entries); - DCHECK(has_highlighted); - infolist_entries->clear(); - *has_highlighted = false; - - const size_t cursor_index_in_page = - candidate_window.cursor_position() % candidate_window.page_size(); - - for (size_t i = 0; i < candidate_window.candidates().size(); ++i) { - const ui::CandidateWindow::Entry& ibus_entry = - candidate_window.candidates()[i]; - if (ibus_entry.description_title.empty() && - ibus_entry.description_body.empty()) - continue; - InfolistEntry entry(base::UTF8ToUTF16(ibus_entry.description_title), - base::UTF8ToUTF16(ibus_entry.description_body)); - if (i == cursor_index_in_page) { - entry.highlighted = true; - *has_highlighted = true; - } - infolist_entries->push_back(entry); - } -} - void CandidateWindowControllerImpl::UpdateLookupTable( const ui::CandidateWindow& candidate_window, bool visible) { @@ -148,9 +118,8 @@ void CandidateWindowControllerImpl::UpdateLookupTable( candidate_window_view_->ShowLookupTable(); bool has_highlighted = false; - std::vector infolist_entries; - ConvertLookupTableToInfolistEntry(candidate_window, &infolist_entries, - &has_highlighted); + std::vector infolist_entries; + candidate_window.GetInfolistEntries(&infolist_entries, &has_highlighted); // If there is no change, just return. if (latest_infolist_entries_ == infolist_entries) @@ -174,7 +143,7 @@ void CandidateWindowControllerImpl::UpdateLookupTable( if (infolist_window_) { infolist_window_->Relayout(infolist_entries); } else { - infolist_window_ = new InfolistWindow( + infolist_window_ = new ash::ime::InfolistWindow( candidate_window_view_, infolist_entries); infolist_window_->InitWidget(); infolist_window_->GetWidget()->AddObserver(this); diff --git a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h index 9976555..a5eab24 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h +++ b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h @@ -7,21 +7,27 @@ #include "chrome/browser/chromeos/input_method/candidate_window_controller.h" +#include "ash/ime/candidate_window_view.h" #include "base/memory/scoped_ptr.h" #include "base/observer_list.h" -#include "chrome/browser/chromeos/input_method/candidate_window_view.h" -#include "chrome/browser/chromeos/input_method/infolist_window.h" #include "ui/base/ime/chromeos/ibus_bridge.h" +#include "ui/base/ime/infolist_entry.h" #include "ui/views/widget/widget_observer.h" -namespace views { -class Widget; -} // namespace views +namespace ash { +namespace ime { +class InfolistWindow; +} // namespace ime +} // namespace ash namespace ui { class CandidateWindow; } // namespace ui +namespace views { +class Widget; +} // namespace views + namespace chromeos { namespace input_method { @@ -32,7 +38,7 @@ class ModeIndicatorController; // CandidateWindowController controls the CandidateWindow. class CandidateWindowControllerImpl : public CandidateWindowController, - public CandidateWindowView::Observer, + public ash::ime::CandidateWindowView::Observer, public views::WidgetObserver, public IBusPanelCandidateWindowHandlerInterface { public: @@ -47,16 +53,13 @@ class CandidateWindowControllerImpl virtual void Hide() OVERRIDE; protected: - // Converts |candidate_window| to infolist entry models. Sets - // |has_highlighted| to true if infolist_entries contains highlighted entry. - // TODO(mukai): move this method (and tests) to the new InfolistEntry model. static void ConvertLookupTableToInfolistEntry( const ui::CandidateWindow& candidate_window, - std::vector* infolist_entries, + std::vector* infolist_entries, bool* has_highlighted); private: - // CandidateWindowView::Observer implementation. + // ash::ime::CandidateWindowView::Observer implementation. virtual void OnCandidateCommitted(int index) OVERRIDE; // views::WidgetObserver implementation. @@ -75,10 +78,10 @@ class CandidateWindowControllerImpl void InitCandidateWindowView(); // The candidate window view. - CandidateWindowView* candidate_window_view_; + ash::ime::CandidateWindowView* candidate_window_view_; // This is the outer frame of the infolist window view. Owned by the widget. - InfolistWindow* infolist_window_; + ash::ime::InfolistWindow* infolist_window_; gfx::Rect cursor_bounds_; gfx::Rect composition_head_; @@ -88,7 +91,7 @@ class CandidateWindowControllerImpl // The infolist entries and its focused index which currently shown in // Infolist window. - std::vector latest_infolist_entries_; + std::vector latest_infolist_entries_; ObserverList observers_; diff --git a/chrome/browser/chromeos/input_method/candidate_window_controller_impl_unittest.cc b/chrome/browser/chromeos/input_method/candidate_window_controller_impl_unittest.cc deleted file mode 100644 index a127346..0000000 --- a/chrome/browser/chromeos/input_method/candidate_window_controller_impl_unittest.cc +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2012 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 "chrome/browser/chromeos/input_method/candidate_window_controller_impl.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace chromeos { -namespace input_method { - -namespace { - -const size_t kSampleCandidateSize = 3; -const char* kSampleCandidate[] = { - "Sample Candidate 1", - "Sample Candidate 2", - "Sample Candidate 3", -}; -const char* kSampleDescriptionTitle[] = { - "Sample Description Title 1", - "Sample Description Title 2", - "Sample Description Title 3", -}; -const char* kSampleDescriptionBody[] = { - "Sample Description Body 1", - "Sample Description Body 2", - "Sample Description Body 3", -}; - -class TestableCandidateWindowControllerImpl : - public CandidateWindowControllerImpl { - public: - TestableCandidateWindowControllerImpl() {} - virtual ~TestableCandidateWindowControllerImpl() {} - - // Changes access right for testing. - using CandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry; - - private: - DISALLOW_COPY_AND_ASSIGN(TestableCandidateWindowControllerImpl); -}; - -} // namespace - -class CandidateWindowControllerImplTest : public testing::Test { - public: - CandidateWindowControllerImplTest() - : kScreenRect(gfx::Rect(0, 0, 1000, 1000)) { - } - - virtual ~CandidateWindowControllerImplTest() { - } - - protected: - const gfx::Rect kScreenRect; -}; - -TEST_F(CandidateWindowControllerImplTest, - ConvertLookupTableToInfolistEntryTest_DenseCase) { - ui::CandidateWindow candidate_window; - candidate_window.set_page_size(10); - for (size_t i = 0; i < kSampleCandidateSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - entry.description_title = kSampleDescriptionTitle[i]; - entry.description_body = kSampleDescriptionBody[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - candidate_window.set_cursor_position(1); - - std::vector infolist_entries; - bool has_highlighted = false; - - TestableCandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry( - candidate_window, - &infolist_entries, - &has_highlighted); - - EXPECT_EQ(kSampleCandidateSize, infolist_entries.size()); - EXPECT_TRUE(has_highlighted); - EXPECT_TRUE(infolist_entries[1].highlighted); -} - -TEST_F(CandidateWindowControllerImplTest, - ConvertLookupTableToInfolistEntryTest_SparseCase) { - ui::CandidateWindow candidate_window; - candidate_window.set_page_size(10); - for (size_t i = 0; i < kSampleCandidateSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - - std::vector* candidates = - candidate_window.mutable_candidates(); - (*candidates)[2].description_title = kSampleDescriptionTitle[2]; - (*candidates)[2].description_body = kSampleDescriptionBody[2]; - - candidate_window.set_cursor_position(2); - - std::vector infolist_entries; - bool has_highlighted = false; - - TestableCandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry( - candidate_window, - &infolist_entries, - &has_highlighted); - - // Infolist entries skips empty descriptions, so expected entry size is 1. - EXPECT_EQ(1UL, infolist_entries.size()); - EXPECT_TRUE(has_highlighted); - EXPECT_TRUE(infolist_entries[0].highlighted); -} - -TEST_F(CandidateWindowControllerImplTest, - ConvertLookupTableToInfolistEntryTest_SparseNoSelectionCase) { - ui::CandidateWindow candidate_window; - candidate_window.set_page_size(10); - - for (size_t i = 0; i < kSampleCandidateSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - - std::vector* candidates = - candidate_window.mutable_candidates(); - (*candidates)[2].description_title = kSampleDescriptionTitle[2]; - (*candidates)[2].description_body = kSampleDescriptionBody[2]; - - candidate_window.set_cursor_position(0); - - std::vector infolist_entries; - bool has_highlighted; - - TestableCandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry( - candidate_window, - &infolist_entries, - &has_highlighted); - - // Infolist entries skips empty descriptions, so expected entry size is 1 and - // no highlighted entries. - EXPECT_EQ(1UL, infolist_entries.size()); - EXPECT_FALSE(has_highlighted); - EXPECT_FALSE(infolist_entries[0].highlighted); -} - -TEST_F(CandidateWindowControllerImplTest, - ConvertLookupTableToInfolistEntryTest_NoInfolistCase) { - ui::CandidateWindow candidate_window; - candidate_window.set_page_size(10); - - for (size_t i = 0; i < kSampleCandidateSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - candidate_window.set_cursor_position(1); - - std::vector infolist_entries; - bool has_highlighted = false; - - TestableCandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry( - candidate_window, - &infolist_entries, - &has_highlighted); - - EXPECT_TRUE(infolist_entries.empty()); - EXPECT_FALSE(has_highlighted); -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/candidate_window_view.cc b/chrome/browser/chromeos/input_method/candidate_window_view.cc deleted file mode 100644 index 5848972..0000000 --- a/chrome/browser/chromeos/input_method/candidate_window_view.cc +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright (c) 2012 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 "chrome/browser/chromeos/input_method/candidate_window_view.h" - -#include - -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/chromeos/input_method/candidate_view.h" -#include "chrome/browser/chromeos/input_method/candidate_window_constants.h" -#include "ui/gfx/color_utils.h" -#include "ui/gfx/screen.h" -#include "ui/native_theme/native_theme.h" -#include "ui/views/background.h" -#include "ui/views/border.h" -#include "ui/views/bubble/bubble_frame_view.h" -#include "ui/views/controls/label.h" -#include "ui/views/corewm/window_animations.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/layout/fill_layout.h" - -namespace chromeos { -namespace input_method { - -namespace { - -class CandidateWindowBorder : public views::BubbleBorder { - public: - explicit CandidateWindowBorder(gfx::NativeView parent) - : views::BubbleBorder(views::BubbleBorder::TOP_CENTER, - views::BubbleBorder::NO_SHADOW, - SK_ColorTRANSPARENT), - parent_(parent), - offset_(0) { - set_paint_arrow(views::BubbleBorder::PAINT_NONE); - } - virtual ~CandidateWindowBorder() {} - - void set_offset(int offset) { offset_ = offset; } - - private: - // Overridden from views::BubbleBorder: - virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, - const gfx::Size& content_size) const OVERRIDE { - gfx::Rect bounds(content_size); - bounds.set_origin(gfx::Point( - anchor_rect.x() - offset_, - is_arrow_on_top(arrow()) ? - anchor_rect.bottom() : anchor_rect.y() - content_size.height())); - - // It cannot use the normal logic of arrow offset for horizontal offscreen, - // because the arrow must be in the content's edge. But CandidateWindow has - // to be visible even when |anchor_rect| is out of the screen. - gfx::Rect work_area = gfx::Screen::GetNativeScreen()-> - GetDisplayNearestWindow(parent_).work_area(); - if (bounds.right() > work_area.right()) - bounds.set_x(work_area.right() - bounds.width()); - if (bounds.x() < work_area.x()) - bounds.set_x(work_area.x()); - - return bounds; - } - - virtual gfx::Insets GetInsets() const OVERRIDE { - return gfx::Insets(); - } - - gfx::NativeView parent_; - int offset_; - - DISALLOW_COPY_AND_ASSIGN(CandidateWindowBorder); -}; - -// Computes the page index. For instance, if the page size is 9, and the -// cursor is pointing to 13th candidate, the page index will be 1 (2nd -// page, as the index is zero-origin). Returns -1 on error. -int ComputePageIndex(const ui::CandidateWindow& candidate_window) { - if (candidate_window.page_size() > 0) - return candidate_window.cursor_position() / candidate_window.page_size(); - return -1; -} - -} // namespace - -class InformationTextArea : public views::View { - public: - // InformationTextArea's border is drawn as a separator, it should appear - // at either top or bottom. - enum BorderPosition { - TOP, - BOTTOM - }; - - // Specify the alignment and initialize the control. - InformationTextArea(gfx::HorizontalAlignment align, int min_width) - : min_width_(min_width) { - label_ = new views::Label; - label_->SetHorizontalAlignment(align); - label_->set_border(views::Border::CreateEmptyBorder(2, 2, 2, 4)); - - SetLayoutManager(new views::FillLayout()); - AddChildView(label_); - set_background(views::Background::CreateSolidBackground( - color_utils::AlphaBlend(SK_ColorBLACK, - GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_WindowBackground), - 0x10))); - } - - // Sets the text alignment. - void SetAlignment(gfx::HorizontalAlignment alignment) { - label_->SetHorizontalAlignment(alignment); - } - - // Sets the displayed text. - void SetText(const std::string& utf8_text) { - label_->SetText(base::UTF8ToUTF16(utf8_text)); - } - - // Sets the border thickness for top/bottom. - void SetBorder(BorderPosition position) { - set_border(views::Border::CreateSolidSidedBorder( - (position == TOP) ? 1 : 0, 0, (position == BOTTOM) ? 1 : 0, 0, - GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_MenuBorderColor))); - } - - protected: - virtual gfx::Size GetPreferredSize() OVERRIDE { - gfx::Size size = views::View::GetPreferredSize(); - size.SetToMax(gfx::Size(min_width_, 0)); - return size; - } - - private: - views::Label* label_; - int min_width_; - - DISALLOW_COPY_AND_ASSIGN(InformationTextArea); -}; - -CandidateWindowView::CandidateWindowView(gfx::NativeView parent) - : selected_candidate_index_in_page_(-1), - should_show_at_composition_head_(false), - should_show_upper_side_(false), - was_candidate_window_open_(false) { - set_parent_window(parent); - set_margins(gfx::Insets()); - - // Set the background and the border of the view. - ui::NativeTheme* theme = GetNativeTheme(); - set_background( - views::Background::CreateSolidBackground(theme->GetSystemColor( - ui::NativeTheme::kColorId_WindowBackground))); - set_border(views::Border::CreateSolidBorder( - 1, theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBorderColor))); - - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); - auxiliary_text_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0); - preedit_ = new InformationTextArea(gfx::ALIGN_LEFT, kMinPreeditAreaWidth); - candidate_area_ = new views::View; - auxiliary_text_->SetVisible(false); - preedit_->SetVisible(false); - candidate_area_->SetVisible(false); - preedit_->SetBorder(InformationTextArea::BOTTOM); - if (candidate_window_.orientation() == ui::CandidateWindow::VERTICAL) { - AddChildView(preedit_); - AddChildView(candidate_area_); - AddChildView(auxiliary_text_); - auxiliary_text_->SetBorder(InformationTextArea::TOP); - candidate_area_->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kVertical, 0, 0, 0)); - } else { - AddChildView(preedit_); - AddChildView(auxiliary_text_); - AddChildView(candidate_area_); - auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT); - auxiliary_text_->SetBorder(InformationTextArea::BOTTOM); - candidate_area_->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kHorizontal, 0, 0, 0)); - } -} - -CandidateWindowView::~CandidateWindowView() { -} - -views::Widget* CandidateWindowView::InitWidget() { - views::Widget* widget = BubbleDelegateView::CreateBubble(this); - - views::corewm::SetWindowVisibilityAnimationType( - widget->GetNativeView(), - views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); - - GetBubbleFrameView()->SetBubbleBorder( - new CandidateWindowBorder(parent_window())); - return widget; -} - -void CandidateWindowView::UpdateVisibility() { - if (candidate_area_->visible() || auxiliary_text_->visible() || - preedit_->visible()) { - SizeToContents(); - } else { - GetWidget()->Close(); - } -} - -void CandidateWindowView::HideLookupTable() { - candidate_area_->SetVisible(false); - auxiliary_text_->SetVisible(false); - UpdateVisibility(); -} - -void CandidateWindowView::HidePreeditText() { - preedit_->SetVisible(false); - UpdateVisibility(); -} - -void CandidateWindowView::ShowPreeditText() { - preedit_->SetVisible(true); - UpdateVisibility(); -} - -void CandidateWindowView::UpdatePreeditText(const std::string& utf8_text) { - preedit_->SetText(utf8_text); -} - -void CandidateWindowView::ShowLookupTable() { - candidate_area_->SetVisible(true); - auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible()); - UpdateVisibility(); -} - -void CandidateWindowView::UpdateCandidates( - const ui::CandidateWindow& new_candidate_window) { - // Updating the candidate views is expensive. We'll skip this if possible. - if (!candidate_window_.IsEqual(new_candidate_window)) { - if (candidate_window_.orientation() != new_candidate_window.orientation()) { - // If the new layout is vertical, the aux text should appear at the - // bottom. If horizontal, it should appear between preedit and candidates. - if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { - ReorderChildView(auxiliary_text_, -1); - auxiliary_text_->SetAlignment(gfx::ALIGN_RIGHT); - auxiliary_text_->SetBorder(InformationTextArea::TOP); - candidate_area_->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kVertical, 0, 0, 0)); - } else { - ReorderChildView(auxiliary_text_, 1); - auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT); - auxiliary_text_->SetBorder(InformationTextArea::BOTTOM); - candidate_area_->SetLayoutManager(new views::BoxLayout( - views::BoxLayout::kHorizontal, 0, 0, 0)); - } - } - - // Initialize candidate views if necessary. - MaybeInitializeCandidateViews(new_candidate_window); - - should_show_at_composition_head_ - = new_candidate_window.show_window_at_composition(); - // Compute the index of the current page. - const int current_page_index = ComputePageIndex(new_candidate_window); - if (current_page_index < 0) - return; - - // Update the candidates in the current page. - const size_t start_from = - current_page_index * new_candidate_window.page_size(); - - int max_shortcut_width = 0; - int max_candidate_width = 0; - for (size_t i = 0; i < candidate_views_.size(); ++i) { - const size_t index_in_page = i; - const size_t candidate_index = start_from + index_in_page; - CandidateView* candidate_view = candidate_views_[index_in_page]; - // Set the candidate text. - if (candidate_index < new_candidate_window.candidates().size()) { - const ui::CandidateWindow::Entry& entry = - new_candidate_window.candidates()[candidate_index]; - candidate_view->SetEntry(entry); - candidate_view->SetState(views::Button::STATE_NORMAL); - candidate_view->SetInfolistIcon(!entry.description_title.empty()); - } else { - // Disable the empty row. - candidate_view->SetEntry(ui::CandidateWindow::Entry()); - candidate_view->SetState(views::Button::STATE_DISABLED); - candidate_view->SetInfolistIcon(false); - } - if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { - int shortcut_width = 0; - int candidate_width = 0; - candidate_views_[i]->GetPreferredWidths( - &shortcut_width, &candidate_width); - max_shortcut_width = std::max(max_shortcut_width, shortcut_width); - max_candidate_width = std::max(max_candidate_width, candidate_width); - } - } - if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) { - for (size_t i = 0; i < candidate_views_.size(); ++i) - candidate_views_[i]->SetWidths(max_shortcut_width, max_candidate_width); - } - - CandidateWindowBorder* border = static_cast( - GetBubbleFrameView()->bubble_border()); - if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) - border->set_offset(max_shortcut_width); - else - border->set_offset(0); - } - // Update the current candidate window. We'll use candidate_window_ from here. - // Note that SelectCandidateAt() uses candidate_window_. - candidate_window_.CopyFrom(new_candidate_window); - - // Select the current candidate in the page. - if (candidate_window_.is_cursor_visible()) { - if (candidate_window_.page_size()) { - const int current_candidate_in_page = - candidate_window_.cursor_position() % candidate_window_.page_size(); - SelectCandidateAt(current_candidate_in_page); - } - } else { - // Unselect the currently selected candidate. - if (0 <= selected_candidate_index_in_page_ && - static_cast(selected_candidate_index_in_page_) < - candidate_views_.size()) { - candidate_views_[selected_candidate_index_in_page_]->SetState( - views::Button::STATE_NORMAL); - selected_candidate_index_in_page_ = -1; - } - } - - // Updates auxiliary text - auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible()); - auxiliary_text_->SetText(candidate_window_.auxiliary_text()); -} - -void CandidateWindowView::SetCursorBounds(const gfx::Rect& cursor_bounds, - const gfx::Rect& composition_head) { - if (candidate_window_.show_window_at_composition()) - SetAnchorRect(composition_head); - else - SetAnchorRect(cursor_bounds); -} - -void CandidateWindowView::MaybeInitializeCandidateViews( - const ui::CandidateWindow& candidate_window) { - const ui::CandidateWindow::Orientation orientation = - candidate_window.orientation(); - const size_t page_size = candidate_window.page_size(); - - // Reset all candidate_views_ when orientation changes. - if (orientation != candidate_window_.orientation()) - STLDeleteElements(&candidate_views_); - - while (page_size < candidate_views_.size()) { - delete candidate_views_.back(); - candidate_views_.pop_back(); - } - while (page_size > candidate_views_.size()) { - CandidateView* new_candidate = new CandidateView(this, orientation); - candidate_area_->AddChildView(new_candidate); - candidate_views_.push_back(new_candidate); - } -} - -void CandidateWindowView::SelectCandidateAt(int index_in_page) { - const int current_page_index = ComputePageIndex(candidate_window_); - if (current_page_index < 0) { - return; - } - - const int cursor_absolute_index = - candidate_window_.page_size() * current_page_index + index_in_page; - // Ignore click on out of range views. - if (cursor_absolute_index < 0 || - candidate_window_.candidates().size() <= - static_cast(cursor_absolute_index)) { - return; - } - - // Remember the currently selected candidate index in the current page. - selected_candidate_index_in_page_ = index_in_page; - - // Select the candidate specified by index_in_page. - candidate_views_[index_in_page]->SetState(views::Button::STATE_PRESSED); - - // Update the cursor indexes in the model. - candidate_window_.set_cursor_position(cursor_absolute_index); -} - -void CandidateWindowView::ButtonPressed(views::Button* sender, - const ui::Event& event) { - for (size_t i = 0; i < candidate_views_.size(); ++i) { - if (sender == candidate_views_[i]) { - FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i)); - return; - } - } -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/candidate_window_view.h b/chrome/browser/chromeos/input_method/candidate_window_view.h deleted file mode 100644 index 98fcc84..0000000 --- a/chrome/browser/chromeos/input_method/candidate_window_view.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2012 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. - -#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_VIEW_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_VIEW_H_ - -#include "ui/base/ime/candidate_window.h" -#include "ui/views/bubble/bubble_delegate.h" -#include "ui/views/controls/button/button.h" - -namespace chromeos { -namespace input_method { - -class CandidateView; -class InformationTextArea; - -// CandidateWindowView is the main container of the candidate window UI. -class CandidateWindowView : public views::BubbleDelegateView, - public views::ButtonListener { - public: - // The object can be monitored by the observer. - class Observer { - public: - virtual ~Observer() {} - // The function is called when a candidate is committed. - virtual void OnCandidateCommitted(int index) = 0; - }; - - explicit CandidateWindowView(gfx::NativeView parent); - virtual ~CandidateWindowView(); - views::Widget* InitWidget(); - - // Adds the given observer. The ownership is not transferred. - void AddObserver(Observer* observer) { - observers_.AddObserver(observer); - } - - // Removes the given observer. - void RemoveObserver(Observer* observer) { - observers_.RemoveObserver(observer); - } - - // Hides the lookup table. - void HideLookupTable(); - - // Hides the auxiliary text. - void HideAuxiliaryText(); - - // Hides the preedit text. - void HidePreeditText(); - - // Shows the lookup table. - void ShowLookupTable(); - - // Shows the auxiliary text. - void ShowAuxiliaryText(); - - // Shows the preedit text. - void ShowPreeditText(); - - // Updates the preedit text. - void UpdatePreeditText(const std::string& utf8_text); - - // Updates candidates of the candidate window from |candidate_window|. - // Candidates are arranged per |orientation|. - void UpdateCandidates(const ui::CandidateWindow& candidate_window); - - void SetCursorBounds(const gfx::Rect& cursor_bounds, - const gfx::Rect& composition_head); - - private: - friend class CandidateWindowViewTest; - - // Overridden from views::ButtonListener: - virtual void ButtonPressed(views::Button* sender, - const ui::Event& event) OVERRIDE; - - void SelectCandidateAt(int index_in_page); - void UpdateVisibility(); - - // Initializes the candidate views if needed. - void MaybeInitializeCandidateViews( - const ui::CandidateWindow& candidate_window); - - // The candidate window data model. - ui::CandidateWindow candidate_window_; - - // The index in the current page of the candidate currently being selected. - int selected_candidate_index_in_page_; - - // The observers of the object. - ObserverList observers_; - - // Views created in the class will be part of tree of |this|, so these - // child views will be deleted when |this| is deleted. - InformationTextArea* auxiliary_text_; - InformationTextArea* preedit_; - views::View* candidate_area_; - - // The candidate views are used for rendering candidates. - std::vector candidate_views_; - - // Current columns size in |candidate_area_|. - gfx::Size previous_shortcut_column_size_; - gfx::Size previous_candidate_column_size_; - gfx::Size previous_annotation_column_size_; - - // The last cursor bounds. - gfx::Rect cursor_bounds_; - - // The last compostion head bounds. - gfx::Rect composition_head_bounds_; - - // True if the candidate window should be shown with aligning with composition - // text as opposed to the cursor. - bool should_show_at_composition_head_; - - // True if the candidate window should be shonw on the upper side of - // composition text. - bool should_show_upper_side_; - - // True if the candidate window was open. This is used to determine when to - // send OnCandidateWindowOpened and OnCandidateWindowClosed events. - bool was_candidate_window_open_; - - DISALLOW_COPY_AND_ASSIGN(CandidateWindowView); -}; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_VIEW_H_ diff --git a/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc b/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc deleted file mode 100644 index bc4fc18..0000000 --- a/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (c) 2012 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 "chrome/browser/chromeos/input_method/candidate_window_view.h" - -#include - -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/chromeos/input_method/candidate_view.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/views/test/views_test_base.h" -#include "ui/views/widget/widget.h" - -namespace chromeos { -namespace input_method { - -namespace { -const char* kSampleCandidate[] = { - "Sample Candidate 1", - "Sample Candidate 2", - "Sample Candidate 3" -}; -const char* kSampleAnnotation[] = { - "Sample Annotation 1", - "Sample Annotation 2", - "Sample Annotation 3" -}; -const char* kSampleDescriptionTitle[] = { - "Sample Description Title 1", - "Sample Description Title 2", - "Sample Description Title 3", -}; -const char* kSampleDescriptionBody[] = { - "Sample Description Body 1", - "Sample Description Body 2", - "Sample Description Body 3", -}; - -void InitCandidateWindow(size_t page_size, - ui::CandidateWindow* candidate_window) { - candidate_window->set_cursor_position(0); - candidate_window->set_page_size(page_size); - candidate_window->mutable_candidates()->clear(); - candidate_window->set_orientation(ui::CandidateWindow::VERTICAL); -} - -void InitCandidateWindowWithCandidatesFilled( - size_t page_size, - ui::CandidateWindow* candidate_window) { - InitCandidateWindow(page_size, candidate_window); - for (size_t i = 0; i < page_size; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = base::StringPrintf("value %lld", - static_cast(i)); - entry.label = base::StringPrintf("%lld", - static_cast(i)); - candidate_window->mutable_candidates()->push_back(entry); - } -} - -} // namespace - -class CandidateWindowViewTest : public views::ViewsTestBase { - public: - CandidateWindowViewTest() {} - virtual ~CandidateWindowViewTest() {} - - protected: - virtual void SetUp() { - views::ViewsTestBase::SetUp(); - candidate_window_view_ = new CandidateWindowView(GetContext()); - candidate_window_view_->InitWidget(); - } - - CandidateWindowView* candidate_window_view() { - return candidate_window_view_; - } - - int selected_candidate_index_in_page() { - return candidate_window_view_->selected_candidate_index_in_page_; - } - - size_t GetCandidatesSize() const { - return candidate_window_view_->candidate_views_.size(); - } - - CandidateView* GetCandidateAt(size_t i) { - return candidate_window_view_->candidate_views_[i]; - } - - void SelectCandidateAt(int index_in_page) { - candidate_window_view_->SelectCandidateAt(index_in_page); - } - - void MaybeInitializeCandidateViews( - const ui::CandidateWindow& candidate_window) { - candidate_window_view_->MaybeInitializeCandidateViews(candidate_window); - } - - void ExpectLabels(const std::string& shortcut, - const std::string& candidate, - const std::string& annotation, - const CandidateView* row) { - EXPECT_EQ(shortcut, base::UTF16ToUTF8(row->shortcut_label_->text())); - EXPECT_EQ(candidate, base::UTF16ToUTF8(row->candidate_label_->text())); - EXPECT_EQ(annotation, base::UTF16ToUTF8(row->annotation_label_->text())); - } - - private: - // owned by |parent_|. - CandidateWindowView* candidate_window_view_; - - DISALLOW_COPY_AND_ASSIGN(CandidateWindowViewTest); -}; - -TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility) { - // Visible (by default) cursor. - ui::CandidateWindow candidate_window; - const int candidate_window_size = 9; - InitCandidateWindowWithCandidatesFilled(candidate_window_size, - &candidate_window); - candidate_window_view()->UpdateCandidates(candidate_window); - EXPECT_EQ(0, selected_candidate_index_in_page()); - - // Invisible cursor. - candidate_window.set_is_cursor_visible(false); - candidate_window_view()->UpdateCandidates(candidate_window); - EXPECT_EQ(-1, selected_candidate_index_in_page()); - - // Move the cursor to the end. - candidate_window.set_cursor_position(candidate_window_size - 1); - candidate_window_view()->UpdateCandidates(candidate_window); - EXPECT_EQ(-1, selected_candidate_index_in_page()); - - // Change the cursor to visible. The cursor must be at the end. - candidate_window.set_is_cursor_visible(true); - candidate_window_view()->UpdateCandidates(candidate_window); - EXPECT_EQ(candidate_window_size - 1, selected_candidate_index_in_page()); -} - -TEST_F(CandidateWindowViewTest, SelectCandidateAtTest) { - // Set 9 candidates. - ui::CandidateWindow candidate_window_large; - const int candidate_window_large_size = 9; - InitCandidateWindowWithCandidatesFilled(candidate_window_large_size, - &candidate_window_large); - candidate_window_large.set_cursor_position(candidate_window_large_size - 1); - candidate_window_view()->UpdateCandidates(candidate_window_large); - - // Select the last candidate. - SelectCandidateAt(candidate_window_large_size - 1); - - // Reduce the number of candidates to 3. - ui::CandidateWindow candidate_window_small; - const int candidate_window_small_size = 3; - InitCandidateWindowWithCandidatesFilled(candidate_window_small_size, - &candidate_window_small); - candidate_window_small.set_cursor_position(candidate_window_small_size - 1); - // Make sure the test doesn't crash if the candidate window reduced - // its size. (crbug.com/174163) - candidate_window_view()->UpdateCandidates(candidate_window_small); - SelectCandidateAt(candidate_window_small_size - 1); -} - -TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { - const char* kEmptyLabel = ""; - const char* kCustomizedLabel[] = { "a", "s", "d" }; - const char* kExpectedHorizontalCustomizedLabel[] = { "a.", "s.", "d." }; - - { - SCOPED_TRACE("candidate_views allocation test"); - const size_t kMaxPageSize = 16; - for (size_t i = 1; i < kMaxPageSize; ++i) { - ui::CandidateWindow candidate_window; - InitCandidateWindow(i, &candidate_window); - candidate_window_view()->UpdateCandidates(candidate_window); - EXPECT_EQ(i, GetCandidatesSize()); - } - } - { - SCOPED_TRACE("Empty string for each labels expects empty labels(vertical)"); - const size_t kPageSize = 3; - ui::CandidateWindow candidate_window; - InitCandidateWindow(kPageSize, &candidate_window); - - candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); - for (size_t i = 0; i < kPageSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - entry.annotation = kSampleAnnotation[i]; - entry.description_title = kSampleDescriptionTitle[i]; - entry.description_body = kSampleDescriptionBody[i]; - entry.label = kEmptyLabel; - candidate_window.mutable_candidates()->push_back(entry); - } - - candidate_window_view()->UpdateCandidates(candidate_window); - - ASSERT_EQ(kPageSize, GetCandidatesSize()); - for (size_t i = 0; i < kPageSize; ++i) { - ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], - GetCandidateAt(i)); - } - } - { - SCOPED_TRACE( - "Empty string for each labels expect empty labels(horizontal)"); - const size_t kPageSize = 3; - ui::CandidateWindow candidate_window; - InitCandidateWindow(kPageSize, &candidate_window); - - candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL); - for (size_t i = 0; i < kPageSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - entry.annotation = kSampleAnnotation[i]; - entry.description_title = kSampleDescriptionTitle[i]; - entry.description_body = kSampleDescriptionBody[i]; - entry.label = kEmptyLabel; - candidate_window.mutable_candidates()->push_back(entry); - } - - candidate_window_view()->UpdateCandidates(candidate_window); - - ASSERT_EQ(kPageSize, GetCandidatesSize()); - // Confirm actual labels not containing ".". - for (size_t i = 0; i < kPageSize; ++i) { - ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], - GetCandidateAt(i)); - } - } - { - SCOPED_TRACE("Vertical customized label case"); - const size_t kPageSize = 3; - ui::CandidateWindow candidate_window; - InitCandidateWindow(kPageSize, &candidate_window); - - candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); - for (size_t i = 0; i < kPageSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - entry.annotation = kSampleAnnotation[i]; - entry.description_title = kSampleDescriptionTitle[i]; - entry.description_body = kSampleDescriptionBody[i]; - entry.label = kCustomizedLabel[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - - candidate_window_view()->UpdateCandidates(candidate_window); - - ASSERT_EQ(kPageSize, GetCandidatesSize()); - // Confirm actual labels not containing ".". - for (size_t i = 0; i < kPageSize; ++i) { - ExpectLabels(kCustomizedLabel[i], - kSampleCandidate[i], - kSampleAnnotation[i], - GetCandidateAt(i)); - } - } - { - SCOPED_TRACE("Horizontal customized label case"); - const size_t kPageSize = 3; - ui::CandidateWindow candidate_window; - InitCandidateWindow(kPageSize, &candidate_window); - - candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL); - for (size_t i = 0; i < kPageSize; ++i) { - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate[i]; - entry.annotation = kSampleAnnotation[i]; - entry.description_title = kSampleDescriptionTitle[i]; - entry.description_body = kSampleDescriptionBody[i]; - entry.label = kCustomizedLabel[i]; - candidate_window.mutable_candidates()->push_back(entry); - } - - candidate_window_view()->UpdateCandidates(candidate_window); - - ASSERT_EQ(kPageSize, GetCandidatesSize()); - // Confirm actual labels not containing ".". - for (size_t i = 0; i < kPageSize; ++i) { - ExpectLabels(kExpectedHorizontalCustomizedLabel[i], - kSampleCandidate[i], - kSampleAnnotation[i], - GetCandidateAt(i)); - } - } -} - -TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { - const size_t kPageSize = 10; - ui::CandidateWindow candidate_window; - ui::CandidateWindow no_shortcut_candidate_window; - - const char kSampleCandidate1[] = "Sample String 1"; - const char kSampleCandidate2[] = "\xE3\x81\x82"; // multi byte string. - const char kSampleCandidate3[] = "....."; - - const char kSampleShortcut1[] = "1"; - const char kSampleShortcut2[] = "b"; - const char kSampleShortcut3[] = "C"; - - const char kSampleAnnotation1[] = "Sample Annotation 1"; - const char kSampleAnnotation2[] = "\xE3\x81\x82"; // multi byte string. - const char kSampleAnnotation3[] = "......"; - - // Create CandidateWindow object. - InitCandidateWindow(kPageSize, &candidate_window); - - candidate_window.set_cursor_position(0); - candidate_window.set_page_size(3); - candidate_window.mutable_candidates()->clear(); - candidate_window.set_orientation(ui::CandidateWindow::VERTICAL); - no_shortcut_candidate_window.CopyFrom(candidate_window); - - ui::CandidateWindow::Entry entry; - entry.value = kSampleCandidate1; - entry.annotation = kSampleAnnotation1; - candidate_window.mutable_candidates()->push_back(entry); - entry.label = kSampleShortcut1; - no_shortcut_candidate_window.mutable_candidates()->push_back(entry); - - entry.value = kSampleCandidate2; - entry.annotation = kSampleAnnotation2; - candidate_window.mutable_candidates()->push_back(entry); - entry.label = kSampleShortcut2; - no_shortcut_candidate_window.mutable_candidates()->push_back(entry); - - entry.value = kSampleCandidate3; - entry.annotation = kSampleAnnotation3; - candidate_window.mutable_candidates()->push_back(entry); - entry.label = kSampleShortcut3; - no_shortcut_candidate_window.mutable_candidates()->push_back(entry); - - int before_height = 0; - - // Test for shortcut mode to no-shortcut mode. - // Initialize with a shortcut mode candidate window. - MaybeInitializeCandidateViews(candidate_window); - ASSERT_EQ(3UL, GetCandidatesSize()); - // Check the selected index is invalidated. - EXPECT_EQ(-1, selected_candidate_index_in_page()); - before_height = - GetCandidateAt(0)->GetContentsBounds().height(); - // Checks all entry have same row height. - for (size_t i = 1; i < GetCandidatesSize(); ++i) - EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); - - // Initialize with a no shortcut mode candidate window. - MaybeInitializeCandidateViews(no_shortcut_candidate_window); - ASSERT_EQ(3UL, GetCandidatesSize()); - // Check the selected index is invalidated. - EXPECT_EQ(-1, selected_candidate_index_in_page()); - EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height()); - // Checks all entry have same row height. - for (size_t i = 1; i < GetCandidatesSize(); ++i) - EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); - - // Test for no-shortcut mode to shortcut mode. - // Initialize with a no shortcut mode candidate window. - MaybeInitializeCandidateViews(no_shortcut_candidate_window); - ASSERT_EQ(3UL, GetCandidatesSize()); - // Check the selected index is invalidated. - EXPECT_EQ(-1, selected_candidate_index_in_page()); - before_height = GetCandidateAt(0)->GetContentsBounds().height(); - // Checks all entry have same row height. - for (size_t i = 1; i < GetCandidatesSize(); ++i) - EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); - - // Initialize with a shortcut mode candidate window. - MaybeInitializeCandidateViews(candidate_window); - ASSERT_EQ(3UL, GetCandidatesSize()); - // Check the selected index is invalidated. - EXPECT_EQ(-1, selected_candidate_index_in_page()); - EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height()); - // Checks all entry have same row height. - for (size_t i = 1; i < GetCandidatesSize(); ++i) - EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); -} -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/infolist_window.cc b/chrome/browser/chromeos/input_method/infolist_window.cc deleted file mode 100644 index f9e4ad5..0000000 --- a/chrome/browser/chromeos/input_method/infolist_window.cc +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2013 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 "chrome/browser/chromeos/input_method/infolist_window.h" - -#include -#include - -#include "base/logging.h" -#include "chrome/browser/chromeos/input_method/candidate_window_constants.h" -#include "grit/generated_resources.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/gfx/color_utils.h" -#include "ui/gfx/font.h" -#include "ui/native_theme/native_theme.h" -#include "ui/views/background.h" -#include "ui/views/border.h" -#include "ui/views/bubble/bubble_border.h" -#include "ui/views/bubble/bubble_frame_view.h" -#include "ui/views/controls/label.h" -#include "ui/views/corewm/window_animations.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/widget/widget.h" - -namespace chromeos { -namespace input_method { - -namespace { -// The width of an info-list. -const int kInfolistEntryWidth = 200; - -// The milliseconds of the delay to show the infolist window. -const int kInfolistShowDelayMilliSeconds = 500; -// The milliseconds of the delay to hide the infolist window. -const int kInfolistHideDelayMilliSeconds = 500; - -/////////////////////////////////////////////////////////////////////////////// -// InfolistBorder -// The BubbleBorder subclass to draw the border and determine its position. -class InfolistBorder : public views::BubbleBorder { - public: - InfolistBorder(); - virtual ~InfolistBorder(); - - // views::BubbleBorder implementation. - virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, - const gfx::Size& contents_size) const OVERRIDE; - virtual gfx::Insets GetInsets() const OVERRIDE; - - private: - DISALLOW_COPY_AND_ASSIGN(InfolistBorder); -}; - -InfolistBorder::InfolistBorder() - : views::BubbleBorder(views::BubbleBorder::LEFT_CENTER, - views::BubbleBorder::NO_SHADOW, - SK_ColorTRANSPARENT) { - set_paint_arrow(views::BubbleBorder::PAINT_NONE); -} - -InfolistBorder::~InfolistBorder() {} - -gfx::Rect InfolistBorder::GetBounds(const gfx::Rect& anchor_rect, - const gfx::Size& contents_size) const { - gfx::Rect bounds(contents_size); - bounds.set_x(is_arrow_on_left(arrow()) ? - anchor_rect.right() : anchor_rect.x() - contents_size.width()); - // InfolistBorder modifies the vertical position based on the arrow offset - // although it doesn't draw the arrow. The arrow offset is the half of - // |contents_size| by default but can be modified through the off-screen logic - // in BubbleFrameView. - bounds.set_y(anchor_rect.y() + contents_size.height() / 2 - - GetArrowOffset(contents_size)); - return bounds; -} - -gfx::Insets InfolistBorder::GetInsets() const { - // This has to be specified and return empty insets to place the infolist - // window without the gap. - return gfx::Insets(); -} - -} // namespace - -// InfolistRow renderes a row of a infolist. -class InfolistEntryView : public views::View { - public: - InfolistEntryView(const InfolistEntry& entry, - const gfx::FontList& title_font, - const gfx::FontList& description_font); - virtual ~InfolistEntryView(); - - void SetEntry(const InfolistEntry& entry); - - private: - // views::View implementation. - virtual gfx::Size GetPreferredSize() OVERRIDE; - - void UpdateBackground(); - - InfolistEntry entry_; - - // The title label. Owned by views hierarchy. - views::Label* title_label_; - - // The description label. Owned by views hierarchy. - views::Label* description_label_; - - DISALLOW_COPY_AND_ASSIGN(InfolistEntryView); -}; - -InfolistEntryView::InfolistEntryView(const InfolistEntry& entry, - const gfx::FontList& title_font, - const gfx::FontList& description_font) - : entry_(entry) { - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); - - title_label_ = new views::Label(entry.title); - title_label_->SetPosition(gfx::Point(0, 0)); - title_label_->SetFontList(title_font); - title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - title_label_->set_border( - views::Border::CreateEmptyBorder(4, 7, 2, 4)); - - description_label_ = new views::Label(entry.body); - description_label_->SetPosition(gfx::Point(0, 0)); - description_label_->SetFontList(description_font); - description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - description_label_->SetMultiLine(true); - description_label_->SizeToFit(kInfolistEntryWidth); - description_label_->set_border( - views::Border::CreateEmptyBorder(2, 17, 4, 4)); - AddChildView(title_label_); - AddChildView(description_label_); - UpdateBackground(); -} - -InfolistEntryView::~InfolistEntryView() {} - -void InfolistEntryView::SetEntry(const InfolistEntry& entry) { - if (entry_ == entry) - return; - - entry_ = entry; - title_label_->SetText(entry_.title); - description_label_->SetText(entry_.body); - UpdateBackground(); -} - -gfx::Size InfolistEntryView::GetPreferredSize() { - return gfx::Size(kInfolistEntryWidth, GetHeightForWidth(kInfolistEntryWidth)); -} - -void InfolistEntryView::UpdateBackground() { - if (entry_.highlighted) { - set_background( - views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused))); - set_border( - views::Border::CreateSolidBorder(1, GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor))); - } else { - set_background(NULL); - set_border(views::Border::CreateEmptyBorder(1, 1, 1, 1)); - } - SchedulePaint(); -} - -/////////////////////////////////////////////////////////////////////////////// -// InfolistEntry model - -InfolistEntry::InfolistEntry(const base::string16& title, - const base::string16& body) - : title(title), body(body), highlighted(false) {} - -bool InfolistEntry::operator==(const InfolistEntry& other) const { - return title == other.title && body == other.body && - highlighted == other.highlighted; -} - -bool InfolistEntry::operator!=(const InfolistEntry& other) const { - return !(*this == other); -} - -/////////////////////////////////////////////////////////////////////////////// -// InfolistWindow - -InfolistWindow::InfolistWindow(views::View* candidate_window, - const std::vector& entries) - : views::BubbleDelegateView(candidate_window, views::BubbleBorder::NONE), - title_font_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 15)), - description_font_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 11)) { - set_move_with_anchor(true); - set_margins(gfx::Insets()); - - set_background( - views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_WindowBackground))); - set_border( - views::Border::CreateSolidBorder(1, GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_MenuBorderColor))); - - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); - - views::Label* caption_label = new views::Label( - l10n_util::GetStringUTF16(IDS_INPUT_METHOD_INFOLIST_WINDOW_TITLE)); - caption_label->SetFontList( - caption_label->font_list().DeriveFontList(kFontSizeDelta - 2)); - caption_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - caption_label->SetEnabledColor(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_LabelEnabledColor)); - caption_label->set_border(views::Border::CreateEmptyBorder(2, 2, 2, 2)); - caption_label->set_background(views::Background::CreateSolidBackground( - color_utils::AlphaBlend(SK_ColorBLACK, - GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_WindowBackground), - 0x10))); - - AddChildView(caption_label); - - for (size_t i = 0; i < entries.size(); ++i) { - entry_views_.push_back( - new InfolistEntryView(entries[i], title_font_, description_font_)); - AddChildView(entry_views_.back()); - } -} - -InfolistWindow::~InfolistWindow() { -} - -void InfolistWindow::InitWidget() { - views::Widget* widget = views::BubbleDelegateView::CreateBubble(this); - views::corewm::SetWindowVisibilityAnimationType( - widget->GetNativeView(), - views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); - - // BubbleFrameView will be initialized through CreateBubble. - GetBubbleFrameView()->SetBubbleBorder(new InfolistBorder()); - SizeToContents(); -} - -void InfolistWindow::Relayout(const std::vector& entries) { - size_t i = 0; - for (; i < entries.size(); ++i) { - if (i < entry_views_.size()) { - entry_views_[i]->SetEntry(entries[i]); - } else { - InfolistEntryView* new_entry = new InfolistEntryView( - entries[i], title_font_, description_font_); - AddChildView(new_entry); - entry_views_.push_back(new_entry); - } - } - - if (i < entry_views_.size()) { - for (; i < entry_views_.size(); ++i) - delete entry_views_[i]; - entry_views_.resize(entries.size()); - } - - Layout(); - GetBubbleFrameView()->bubble_border()->set_arrow_offset(0); - SizeToContents(); -} - -void InfolistWindow::ShowWithDelay() { - show_hide_timer_.Start( - FROM_HERE, - base::TimeDelta::FromMilliseconds(kInfolistShowDelayMilliSeconds), - GetWidget(), - &views::Widget::Show); -} - -void InfolistWindow::HideWithDelay() { - show_hide_timer_.Start( - FROM_HERE, - base::TimeDelta::FromMilliseconds(kInfolistHideDelayMilliSeconds), - GetWidget(), - &views::Widget::Close); -} - -void InfolistWindow::ShowImmediately() { - show_hide_timer_.Stop(); - GetWidget()->Show(); -} - -void InfolistWindow::HideImmediately() { - show_hide_timer_.Stop(); - GetWidget()->Close(); -} - -void InfolistWindow::WindowClosing() { - show_hide_timer_.Stop(); -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/infolist_window.h b/chrome/browser/chromeos/input_method/infolist_window.h deleted file mode 100644 index 4abf5e3..0000000 --- a/chrome/browser/chromeos/input_method/infolist_window.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2013 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. - -#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_INFOLIST_WINDOW_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_INFOLIST_WINDOW_H_ - -#include -#include - -#include "base/memory/scoped_ptr.h" -#include "base/strings/string16.h" -#include "base/timer/timer.h" -#include "ui/gfx/font_list.h" -#include "ui/views/bubble/bubble_delegate.h" - -namespace chromeos { -namespace input_method { - -class InfolistEntryView; - -// TODO(mukai): move this model to another place, src/chromeos/ime or -// src/ui/base/ime. -struct InfolistEntry { - base::string16 title; - base::string16 body; - bool highlighted; - - InfolistEntry(const base::string16& title, const base::string16& body); - bool operator==(const InfolistEntry& entry) const; - bool operator!=(const InfolistEntry& entry) const; -}; - -// A widget delegate representing the infolist window UI. -class InfolistWindow : public views::BubbleDelegateView { - public: - InfolistWindow(views::View* candidate_window, - const std::vector& entries); - virtual ~InfolistWindow(); - void InitWidget(); - - // Updates infolist contents with |entries|. - void Relayout(const std::vector& entries); - - // Show/hide itself with a delay. - void ShowWithDelay(); - void HideWithDelay(); - - // Show/hide without delays. - void ShowImmediately(); - void HideImmediately(); - - private: - // views::WidgetDelegate implementation. - virtual void WindowClosing() OVERRIDE; - - // The list of visible entries. Owned by views hierarchy. - std::vector entry_views_; - - // Information title font. - gfx::FontList title_font_; - - // Information description font. - gfx::FontList description_font_; - - base::OneShotTimer show_hide_timer_; - - DISALLOW_COPY_AND_ASSIGN(InfolistWindow); -}; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_INFOLIST_WINDOW_H_ diff --git a/chrome/browser/chromeos/input_method/mode_indicator_controller.cc b/chrome/browser/chromeos/input_method/mode_indicator_controller.cc index 1bdc1b1..cd8241e 100644 --- a/chrome/browser/chromeos/input_method/mode_indicator_controller.cc +++ b/chrome/browser/chromeos/input_method/mode_indicator_controller.cc @@ -4,11 +4,14 @@ #include "chrome/browser/chromeos/input_method/mode_indicator_controller.h" +#include "ash/ime/mode_indicator_view.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/window_util.h" #include "base/command_line.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chromeos/input_method/input_method_util.h" -#include "chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h" #include "chromeos/chromeos_switches.h" namespace chromeos { @@ -115,16 +118,19 @@ void ModeIndicatorController::ShowModeIndicator() { const base::string16 short_name = imm_->GetInputMethodUtil()->GetInputMethodShortName(descriptor); - ModeIndicatorDelegateView* mi_delegate_view = - new ModeIndicatorDelegateView(cursor_bounds_, short_name); - views::BubbleDelegateView::CreateBubble(mi_delegate_view); + aura::Window* parent = ash::Shell::GetContainer( + ash::wm::GetActiveWindow()->GetRootWindow(), + ash::internal::kShellWindowId_InputMethodContainer); + ash::ime::ModeIndicatorView* mi_view = new ash::ime::ModeIndicatorView( + parent, cursor_bounds_, short_name); + views::BubbleDelegateView::CreateBubble(mi_view); - views::Widget* mi_widget = mi_delegate_view->GetWidget(); + views::Widget* mi_widget = mi_view->GetWidget(); if (GetModeIndicatorObserverForTesting()) GetModeIndicatorObserverForTesting()->AddModeIndicatorWidget(mi_widget); mi_observer_->AddModeIndicatorWidget(mi_widget); - mi_delegate_view->ShowAndFadeOut(); + mi_view->ShowAndFadeOut(); } } // namespace input_method diff --git a/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.cc b/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.cc deleted file mode 100644 index c9d161f..0000000 --- a/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2013 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 "chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h" - -#include "ash/shell.h" -#include "ash/shell_window_ids.h" -#include "ash/wm/window_animations.h" -#include "ash/wm/window_util.h" -#include "base/logging.h" -#include "ui/views/bubble/bubble_delegate.h" -#include "ui/views/controls/label.h" -#include "ui/views/layout/fill_layout.h" - -namespace chromeos { -namespace input_method { - -namespace { -// Minimum size of inner contents in pixel. -// 43 is the designed size including the default margin (6 * 2). -const int kMinSize = 31; - -// After this duration in msec, the mode inicator will be fading out. -const int kShowingDuration = 500; -} // namespace - - -ModeIndicatorDelegateView::ModeIndicatorDelegateView( - const gfx::Rect& cursor_bounds, - const base::string16& label) - : cursor_bounds_(cursor_bounds), - label_view_(new views::Label(label)) { - set_use_focusless(true); - set_accept_events(false); - set_parent_window( - ash::Shell::GetContainer( - ash::wm::GetActiveWindow()->GetRootWindow(), - ash::internal::kShellWindowId_InputMethodContainer)); - set_shadow(views::BubbleBorder::NO_SHADOW); - set_arrow(views::BubbleBorder::TOP_CENTER); -} - -ModeIndicatorDelegateView::~ModeIndicatorDelegateView() {} - -void ModeIndicatorDelegateView::FadeOut() { - StartFade(false); -} - -void ModeIndicatorDelegateView::ShowAndFadeOut() { - views::corewm::SetWindowVisibilityAnimationTransition( - GetWidget()->GetNativeView(), - views::corewm::ANIMATE_HIDE); - GetWidget()->Show(); - timer_.Start(FROM_HERE, - base::TimeDelta::FromMilliseconds(kShowingDuration), - this, - &ModeIndicatorDelegateView::FadeOut); -} - -gfx::Size ModeIndicatorDelegateView::GetPreferredSize() { - gfx::Size size = label_view_->GetPreferredSize(); - size.SetToMax(gfx::Size(kMinSize, kMinSize)); - return size; -} - -void ModeIndicatorDelegateView::Init() { - SetLayoutManager(new views::FillLayout()); - AddChildView(label_view_); - - SetAnchorRect(cursor_bounds_); -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h b/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h deleted file mode 100644 index bc4c9c2..0000000 --- a/chrome/browser/chromeos/input_method/mode_indicator_delegate_view.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2013 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. - -#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_MODE_INDICATOR_DELEGATE_VIEW_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_MODE_INDICATOR_DELEGATE_VIEW_H_ - -#include "base/strings/string16.h" -#include "base/timer/timer.h" -#include "ui/gfx/rect.h" -#include "ui/views/bubble/bubble_delegate.h" - -namespace views { -class Label; -} // namespace views - -namespace chromeos { -namespace input_method { - -class ModeIndicatorDelegateView : public views::BubbleDelegateView { - public: - ModeIndicatorDelegateView(const gfx::Rect& cursor_bounds, - const base::string16& label); - virtual ~ModeIndicatorDelegateView(); - - // Show the mode indicator then hide with fading animation. - void ShowAndFadeOut(); - - // views::BubbleDelegateView override: - virtual gfx::Size GetPreferredSize() OVERRIDE; - - protected: - // views::BubbleDelegateView override: - virtual void Init() OVERRIDE; - - private: - // Hide the window with fading animation. This is called from - // ShowAndFadeOut. - void FadeOut(); - - gfx::Rect cursor_bounds_; - views::Label* label_view_; - base::OneShotTimer timer_; -}; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_MODE_INDICATOR_DELEGATE_VIEW_H_ diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index 16e8cde..31e8aaf 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -402,16 +402,10 @@ 'browser/chromeos/input_method/accessibility.h', 'browser/chromeos/input_method/browser_state_monitor.cc', 'browser/chromeos/input_method/browser_state_monitor.h', - 'browser/chromeos/input_method/candidate_view.cc', - 'browser/chromeos/input_method/candidate_view.h', 'browser/chromeos/input_method/candidate_window_controller.cc', 'browser/chromeos/input_method/candidate_window_controller.h', 'browser/chromeos/input_method/candidate_window_controller_impl.cc', 'browser/chromeos/input_method/candidate_window_controller_impl.h', - 'browser/chromeos/input_method/candidate_window_view.cc', - 'browser/chromeos/input_method/candidate_window_view.h', - 'browser/chromeos/input_method/infolist_window.cc', - 'browser/chromeos/input_method/infolist_window.h', 'browser/chromeos/input_method/input_method_engine.cc', 'browser/chromeos/input_method/input_method_engine.h', 'browser/chromeos/input_method/input_method_configuration.cc', @@ -430,8 +424,6 @@ 'browser/chromeos/input_method/component_extension_ime_manager_impl.h', 'browser/chromeos/input_method/mode_indicator_controller.cc', 'browser/chromeos/input_method/mode_indicator_controller.h', - 'browser/chromeos/input_method/mode_indicator_delegate_view.cc', - 'browser/chromeos/input_method/mode_indicator_delegate_view.h', 'browser/chromeos/keyboard_driven_event_rewriter.cc', 'browser/chromeos/keyboard_driven_event_rewriter.h', 'browser/chromeos/kiosk_mode/kiosk_mode_idle_logout.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 0c72a92..e6844cc 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -688,8 +688,6 @@ 'browser/chromeos/imageburner/burn_device_handler_unittest.cc', 'browser/chromeos/imageburner/burn_manager_unittest.cc', 'browser/chromeos/input_method/browser_state_monitor_unittest.cc', - 'browser/chromeos/input_method/candidate_window_controller_impl_unittest.cc', - 'browser/chromeos/input_method/candidate_window_view_unittest.cc', 'browser/chromeos/input_method/input_method_configuration_unittest.cc', 'browser/chromeos/input_method/input_method_manager_impl_unittest.cc', 'browser/chromeos/input_method/input_method_persistence_unittest.cc', diff --git a/ui/base/ime/candidate_window.cc b/ui/base/ime/candidate_window.cc index bf287ff..f49613b 100644 --- a/ui/base/ime/candidate_window.cc +++ b/ui/base/ime/candidate_window.cc @@ -6,6 +6,7 @@ #include #include "base/logging.h" +#include "base/strings/utf_string_conversions.h" #include "base/values.h" namespace ui { @@ -53,6 +54,32 @@ void CandidateWindow::CopyFrom(const CandidateWindow& cw) { } +void CandidateWindow::GetInfolistEntries( + std::vector* infolist_entries, + bool* has_highlighted) const { + DCHECK(infolist_entries); + DCHECK(has_highlighted); + infolist_entries->clear(); + *has_highlighted = false; + + const size_t cursor_index_in_page = cursor_position() % page_size(); + + for (size_t i = 0; i < candidates().size(); ++i) { + const CandidateWindow::Entry& candidate_entry = candidates()[i]; + if (candidate_entry.description_title.empty() && + candidate_entry.description_body.empty()) + continue; + + InfolistEntry entry(base::UTF8ToUTF16(candidate_entry.description_title), + base::UTF8ToUTF16(candidate_entry.description_body)); + if (i == cursor_index_in_page) { + entry.highlighted = true; + *has_highlighted = true; + } + infolist_entries->push_back(entry); + } +} + // When the default values are changed, please modify // InputMethodEngineInterface::CandidateWindowProperty too. CandidateWindow::CandidateWindowProperty::CandidateWindowProperty() diff --git a/ui/base/ime/candidate_window.h b/ui/base/ime/candidate_window.h index d7523e1..f4f3851 100644 --- a/ui/base/ime/candidate_window.h +++ b/ui/base/ime/candidate_window.h @@ -9,6 +9,7 @@ #include #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "ui/base/ime/infolist_entry.h" #include "ui/base/ui_base_export.h" namespace ui { @@ -63,6 +64,11 @@ class UI_BASE_EXPORT CandidateWindow { *property_ = property; } + // Gets the infolist entry models. Sets |has_highlighted| to true if |entries| + // contains highlighted entry. + void GetInfolistEntries(std::vector* entries, + bool* has_highlighted) const; + // Returns the number of candidates in one page. uint32 page_size() const { return property_->page_size; } void set_page_size(uint32 page_size) { property_->page_size = page_size; } diff --git a/ui/base/ime/candidate_window_unittest.cc b/ui/base/ime/candidate_window_unittest.cc index 8af1e99..c408f5f 100644 --- a/ui/base/ime/candidate_window_unittest.cc +++ b/ui/base/ime/candidate_window_unittest.cc @@ -14,6 +14,27 @@ namespace ui { +namespace { + +const size_t kSampleCandidateSize = 3; +const char* kSampleCandidate[] = { + "Sample Candidate 1", + "Sample Candidate 2", + "Sample Candidate 3", +}; +const char* kSampleDescriptionTitle[] = { + "Sample Description Title 1", + "Sample Description Title 2", + "Sample Description Title 3", +}; +const char* kSampleDescriptionBody[] = { + "Sample Description Body 1", + "Sample Description Body 2", + "Sample Description Body 3", +}; + +} + TEST(CandidateWindow, IsEqualTest) { CandidateWindow cw1; CandidateWindow cw2; @@ -135,4 +156,102 @@ TEST(CandidateWindow, CopyFromTest) { EXPECT_TRUE(cw1.IsEqual(cw2)); } +TEST(CandidateWindow, GetInfolistEntries_DenseCase) { + CandidateWindow candidate_window; + candidate_window.set_page_size(10); + for (size_t i = 0; i < kSampleCandidateSize; ++i) { + CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + entry.description_title = kSampleDescriptionTitle[i]; + entry.description_body = kSampleDescriptionBody[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + candidate_window.set_cursor_position(1); + + std::vector infolist_entries; + bool has_highlighted = false; + + candidate_window.GetInfolistEntries(&infolist_entries, &has_highlighted); + + EXPECT_EQ(kSampleCandidateSize, infolist_entries.size()); + EXPECT_TRUE(has_highlighted); + EXPECT_TRUE(infolist_entries[1].highlighted); +} + +TEST(CandidateWindow, GetInfolistEntries_SparseCase) { + CandidateWindow candidate_window; + candidate_window.set_page_size(10); + for (size_t i = 0; i < kSampleCandidateSize; ++i) { + CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + + std::vector* candidates = + candidate_window.mutable_candidates(); + (*candidates)[2].description_title = kSampleDescriptionTitle[2]; + (*candidates)[2].description_body = kSampleDescriptionBody[2]; + + candidate_window.set_cursor_position(2); + + std::vector infolist_entries; + bool has_highlighted = false; + + candidate_window.GetInfolistEntries(&infolist_entries, &has_highlighted); + + // Infolist entries skips empty descriptions, so expected entry size is 1. + EXPECT_EQ(1UL, infolist_entries.size()); + EXPECT_TRUE(has_highlighted); + EXPECT_TRUE(infolist_entries[0].highlighted); +} + +TEST(CandidateWindow, GetInfolistEntries_SparseNoSelectionCase) { + CandidateWindow candidate_window; + candidate_window.set_page_size(10); + + for (size_t i = 0; i < kSampleCandidateSize; ++i) { + CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + + std::vector* candidates = + candidate_window.mutable_candidates(); + (*candidates)[2].description_title = kSampleDescriptionTitle[2]; + (*candidates)[2].description_body = kSampleDescriptionBody[2]; + + candidate_window.set_cursor_position(0); + + std::vector infolist_entries; + bool has_highlighted; + + candidate_window.GetInfolistEntries(&infolist_entries, &has_highlighted); + + // Infolist entries skips empty descriptions, so expected entry size is 1 and + // no highlighted entries. + EXPECT_EQ(1UL, infolist_entries.size()); + EXPECT_FALSE(has_highlighted); + EXPECT_FALSE(infolist_entries[0].highlighted); +} + +TEST(CandidateWindow, GetInfolistEntries_NoInfolistCase) { + CandidateWindow candidate_window; + candidate_window.set_page_size(10); + + for (size_t i = 0; i < kSampleCandidateSize; ++i) { + CandidateWindow::Entry entry; + entry.value = kSampleCandidate[i]; + candidate_window.mutable_candidates()->push_back(entry); + } + candidate_window.set_cursor_position(1); + + std::vector infolist_entries; + bool has_highlighted = false; + + candidate_window.GetInfolistEntries(&infolist_entries, &has_highlighted); + + EXPECT_TRUE(infolist_entries.empty()); + EXPECT_FALSE(has_highlighted); +} + } // namespace ui diff --git a/ui/base/ime/ime.gypi b/ui/base/ime/ime.gypi index c3f57ae..a475d99 100644 --- a/ui/base/ime/ime.gypi +++ b/ui/base/ime/ime.gypi @@ -26,6 +26,8 @@ 'composition_underline.h', 'dummy_input_method_delegate.cc', 'dummy_input_method_delegate.h', + 'infolist_entry.cc', + 'infolist_entry.h', 'input_method.h', 'input_method_base.cc', 'input_method_base.h', diff --git a/ui/base/ime/infolist_entry.cc b/ui/base/ime/infolist_entry.cc new file mode 100644 index 0000000..7423f73 --- /dev/null +++ b/ui/base/ime/infolist_entry.cc @@ -0,0 +1,22 @@ +// Copyright 2014 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 "ui/base/ime/infolist_entry.h" + +namespace ui { + +InfolistEntry::InfolistEntry(const base::string16& title, + const base::string16& body) + : title(title), body(body), highlighted(false) {} + +bool InfolistEntry::operator==(const InfolistEntry& other) const { + return title == other.title && body == other.body && + highlighted == other.highlighted; +} + +bool InfolistEntry::operator!=(const InfolistEntry& other) const { + return !(*this == other); +} + +} // namespace ui diff --git a/ui/base/ime/infolist_entry.h b/ui/base/ime/infolist_entry.h new file mode 100644 index 0000000..3cb7672 --- /dev/null +++ b/ui/base/ime/infolist_entry.h @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +#ifndef UI_BASE_IME_INFOLIST_ENTRY_H_ +#define UI_BASE_IME_INFOLIST_ENTRY_H_ + +#include "base/strings/string16.h" +#include "ui/base/ui_base_export.h" + +namespace ui { + +// The data model of infolist window. +struct UI_BASE_EXPORT InfolistEntry { + base::string16 title; + base::string16 body; + bool highlighted; + + InfolistEntry(const base::string16& title, const base::string16& body); + bool operator==(const InfolistEntry& entry) const; + bool operator!=(const InfolistEntry& entry) const; +}; + +} // namespace ui + +#endif // UI_BASE_IME_INFOLIST_ENTRY_H_ -- cgit v1.1