diff options
author | mukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-06 17:03:25 +0000 |
---|---|---|
committer | mukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-06 17:03:25 +0000 |
commit | 1e936b74a528257a3391d4b2a427ee9f2e358368 (patch) | |
tree | 7ab3483212948f112190591c42de50689eb0ace9 | |
parent | 64b1d4eaffb35f10f5fdc591a680036063d9b78d (diff) | |
download | chromium_src-1e936b74a528257a3391d4b2a427ee9f2e358368.zip chromium_src-1e936b74a528257a3391d4b2a427ee9f2e358368.tar.gz chromium_src-1e936b74a528257a3391d4b2a427ee9f2e358368.tar.bz2 |
Cleanup CandidateWindowView.
- moves the implementation of CandidateView as a separate cc file.
- CandidateView is now a button to reduce the click handling.
- introduces canceling drag logic over CandidateViews
- CandidateWindowView is now a BubbleDelegateView
- HidableArea is removed. It's not used anymore.
- removes complex GridLayout logic and use BoxLayout.
BUG=325813
R=komatsu@chromium.org
TEST=unit_tests pass, manually check
Review URL: https://codereview.chromium.org/121163003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243106 0039d316-1c4b-4281-b951-d872f2087c98
10 files changed, 670 insertions, 1269 deletions
diff --git a/chrome/browser/chromeos/input_method/candidate_view.cc b/chrome/browser/chromeos/input_method/candidate_view.cc new file mode 100644 index 0000000..fd655e8 --- /dev/null +++ b/chrome/browser/chromeos/input_method/candidate_view.cc @@ -0,0 +1,286 @@ +// 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 "chromeos/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( + 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 == 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 == 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 == 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( + 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 == 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( + 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, + 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 == 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 CandidateWindow::Entry& entry) { + std::string label = entry.label; + if (!label.empty() && orientation_ != 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<CandidateView*>((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_ == 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_ == CandidateWindow::VERTICAL ? 4 : 6; + gfx::Size size; + if (shortcut_label_->visible()) { + size = shortcut_label_->GetPreferredSize(); + size.Enlarge(padding_width, 0); + } + gfx::Size candidate_size = candidate_label_->GetPreferredSize(); + 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 index d5ad243..4c8834a 100644 --- a/chrome/browser/chromeos/input_method/candidate_view.h +++ b/chrome/browser/chromeos/input_method/candidate_view.h @@ -7,78 +7,46 @@ #include "base/gtest_prod_util.h" #include "chromeos/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 { -class CandidateWindowView; - // CandidateView renderes a row of a candidate. -class CandidateView : public views::View { +class CandidateView : public views::CustomButton { public: - CandidateView(CandidateWindowView* parent_candidate_window, - int index_in_page, + CandidateView(views::ButtonListener* listener, CandidateWindow::Orientation orientation); virtual ~CandidateView() {} - // Initializes the candidate view with the given column widths. - // A width of 0 means that the column is resizable. - void Init(int shortcut_column_width, - int candidate_column_width, - int annotation_column_width, - int column_height); - // Sets candidate text to the given text. - void SetCandidateText(const base::string16& text); + void GetPreferredWidths(int* shortcut_width, + int* candidate_width); - // Sets shortcut text to the given text. - void SetShortcutText(const base::string16& text); + void SetWidths(int shortcut_width, + int candidate_width); - // Sets annotation text to the given text. - void SetAnnotationText(const base::string16& text); + void SetEntry(const CandidateWindow::Entry& entry); // Sets infolist icon. void SetInfolistIcon(bool enable); - // Selects the candidate row. Changes the appearance to make it look - // like a selected candidate. - void Select(); - - // Unselects the candidate row. Changes the appearance to make it look - // like an unselected candidate. - void Unselect(); - - // Enables or disables the candidate row based on |enabled|. Changes the - // appearance to make it look like unclickable area. - void SetRowEnabled(bool enabled); - - // Returns the relative position of the candidate label. - gfx::Point GetCandidateLabelPosition() const; - private: friend class CandidateWindowViewTest; FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, ShortcutSettingTest); - // Overridden from View: - virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE; - virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; - - // Selects the candidate located at the point. - void SelectCandidateAt(const gfx::Point& location); - // Notifies labels of their new background colors. Called whenever the view's - // background color changes. - void UpdateLabelBackgroundColors(); + // Overridden from views::CustomButton: + virtual void StateChanged() OVERRIDE; - // Zero-origin index in the current page. - int index_in_page_; + // 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. CandidateWindow::Orientation orientation_; - // The parent candidate window that contains this view. - CandidateWindowView* parent_candidate_window_; - // Views created in the class will be part of tree of |this|, so these // child views will be deleted when |this| is deleted. @@ -89,9 +57,11 @@ class CandidateView : public views::View { // The annotation label renders annotations. views::Label* annotation_label_; + int shortcut_width_; + int candidate_width_; + // The infolist icon. views::View* infolist_icon_; - bool infolist_icon_enabled_; DISALLOW_COPY_AND_ASSIGN(CandidateView); }; 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 a203a7c..5adab64 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc +++ b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.cc @@ -31,49 +31,38 @@ CandidateWindowControllerImpl::CandidateWindowControllerImpl() : candidate_window_view_(NULL), infolist_window_(NULL) { IBusBridge::Get()->SetCandidateWindowHandler(this); - CreateView(); + // Create the mode indicator controller. + mode_indicator_controller_.reset( + new ModeIndicatorController(InputMethodManager::Get())); } CandidateWindowControllerImpl::~CandidateWindowControllerImpl() { IBusBridge::Get()->SetCandidateWindowHandler(NULL); - candidate_window_view_->RemoveObserver(this); + if (candidate_window_view_) { + candidate_window_view_->RemoveObserver(this); + candidate_window_view_->GetWidget()->RemoveObserver(this); + } } -void CandidateWindowControllerImpl::CreateView() { - // Create a non-decorated frame. - frame_.reset(new views::Widget); - // The size is initially zero. - views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); - // |frame_| is owned by controller impl so - // they should use WIDGET_OWNS_NATIVE_WIDGET ownership. - params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - // Show the candidate window always on top - params.parent = ash::Shell::GetContainer( - ash::Shell::GetTargetRootWindow(), - ash::internal::kShellWindowId_InputMethodContainer); - frame_->Init(params); - - views::corewm::SetWindowVisibilityAnimationType( - frame_->GetNativeView(), - views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); +void CandidateWindowControllerImpl::InitCandidateWindowView() { + if (candidate_window_view_) + return; - // Create the candidate window. - candidate_window_view_ = new CandidateWindowView(frame_.get()); - candidate_window_view_->Init(); + candidate_window_view_ = new CandidateWindowView(ash::Shell::GetContainer( + ash::Shell::GetTargetRootWindow(), + ash::internal::kShellWindowId_InputMethodContainer)); candidate_window_view_->AddObserver(this); - - frame_->SetContentsView(candidate_window_view_); - - // Create the mode indicator controller. - mode_indicator_controller_.reset( - new ModeIndicatorController(InputMethodManager::Get())); + candidate_window_view_->SetCursorBounds(cursor_bounds_, composition_head_); + views::Widget* widget = candidate_window_view_->InitWidget(); + widget->AddObserver(this); + widget->Show(); + FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_, + CandidateWindowOpened()); } void CandidateWindowControllerImpl::Hide() { - // To hide the candidate window we have to call HideLookupTable and - // HideAuxiliaryText. Without calling HideAuxiliaryText the - // auxiliary text area will remain. - candidate_window_view_->HideLookupTable(); + if (candidate_window_view_) + candidate_window_view_->GetWidget()->Close(); if (infolist_window_) infolist_window_->HideImmediately(); } @@ -84,8 +73,10 @@ void CandidateWindowControllerImpl::SetCursorBounds( // A workaround for http://crosbug.com/6460. We should ignore very short Y // move to prevent the window from shaking up and down. const int kKeepPositionThreshold = 2; // px - const gfx::Rect& last_bounds = - candidate_window_view_->cursor_bounds(); + gfx::Rect last_bounds; + if (candidate_window_view_) + last_bounds = candidate_window_view_->GetAnchorRect(); + const int delta_y = abs(last_bounds.y() - cursor_bounds.y()); if ((last_bounds.x() == cursor_bounds.x()) && (delta_y <= kKeepPositionThreshold)) { @@ -93,11 +84,12 @@ void CandidateWindowControllerImpl::SetCursorBounds( return; } + cursor_bounds_ = cursor_bounds; + composition_head_ = composition_head; + // Remember the cursor bounds. - candidate_window_view_->set_cursor_bounds(cursor_bounds); - candidate_window_view_->set_composition_head_bounds(composition_head); - // Move the window per the cursor bounds. - candidate_window_view_->ResizeAndMoveParentFrame(); + if (candidate_window_view_) + candidate_window_view_->SetCursorBounds(cursor_bounds, composition_head); // Mode indicator controller also needs the cursor bounds. mode_indicator_controller_->SetCursorBounds(cursor_bounds); @@ -141,7 +133,8 @@ void CandidateWindowControllerImpl::UpdateLookupTable( bool visible) { // If it's not visible, hide the lookup table and return. if (!visible) { - candidate_window_view_->HideLookupTable(); + if (candidate_window_view_) + candidate_window_view_->HideLookupTable(); if (infolist_window_) infolist_window_->HideImmediately(); // TODO(nona): Introduce unittests for crbug.com/170036. @@ -149,6 +142,8 @@ void CandidateWindowControllerImpl::UpdateLookupTable( return; } + if (!candidate_window_view_) + InitCandidateWindowView(); candidate_window_view_->UpdateCandidates(candidate_window); candidate_window_view_->ShowLookupTable(); @@ -191,9 +186,12 @@ void CandidateWindowControllerImpl::UpdatePreeditText( const std::string& utf8_text, unsigned int cursor, bool visible) { // If it's not visible, hide the preedit text and return. if (!visible || utf8_text.empty()) { - candidate_window_view_->HidePreeditText(); + if (candidate_window_view_) + candidate_window_view_->HidePreeditText(); return; } + if (!candidate_window_view_) + InitCandidateWindowView(); candidate_window_view_->UpdatePreeditText(utf8_text); candidate_window_view_->ShowPreeditText(); } @@ -203,20 +201,17 @@ void CandidateWindowControllerImpl::OnCandidateCommitted(int index) { CandidateClicked(index)); } -void CandidateWindowControllerImpl::OnCandidateWindowOpened() { - FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_, - CandidateWindowOpened()); -} - -void CandidateWindowControllerImpl::OnCandidateWindowClosed() { - FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_, - CandidateWindowClosed()); -} - void CandidateWindowControllerImpl::OnWidgetClosing(views::Widget* widget) { if (infolist_window_ && widget == infolist_window_->GetWidget()) { widget->RemoveObserver(this); infolist_window_ = NULL; + } else if (candidate_window_view_ && + widget == candidate_window_view_->GetWidget()) { + widget->RemoveObserver(this); + candidate_window_view_->RemoveObserver(this); + candidate_window_view_ = NULL; + FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_, + CandidateWindowClosed()); } } 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 5356bb4..5396525 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h +++ b/chrome/browser/chromeos/input_method/candidate_window_controller_impl.h @@ -55,8 +55,6 @@ class CandidateWindowControllerImpl private: // CandidateWindowView::Observer implementation. virtual void OnCandidateCommitted(int index) OVERRIDE; - virtual void OnCandidateWindowOpened() OVERRIDE; - virtual void OnCandidateWindowClosed() OVERRIDE; // views::WidgetObserver implementation. virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE; @@ -70,19 +68,17 @@ class CandidateWindowControllerImpl unsigned int cursor, bool visible) OVERRIDE; virtual void FocusStateChanged(bool is_focused) OVERRIDE; - // Creates the candidate window view. - void CreateView(); + void InitCandidateWindowView(); // The candidate window view. CandidateWindowView* candidate_window_view_; - // This is the outer frame of the candidate window view. The frame will - // own |candidate_window_|. - scoped_ptr<views::Widget> frame_; - // This is the outer frame of the infolist window view. Owned by the widget. InfolistWindow* infolist_window_; + gfx::Rect cursor_bounds_; + gfx::Rect composition_head_; + // This is the controller of the IME mode indicator. scoped_ptr<ModeIndicatorController> mode_indicator_controller_; diff --git a/chrome/browser/chromeos/input_method/candidate_window_view.cc b/chrome/browser/chromeos/input_method/candidate_window_view.cc index bd97f6e..23acaa9 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_view.cc +++ b/chrome/browser/chromeos/input_method/candidate_window_view.cc @@ -5,216 +5,72 @@ #include <string> -#include "ash/shell.h" #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 "chrome/browser/chromeos/input_method/hidable_area.h" #include "chromeos/ime/candidate_window.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/layout/grid_layout.h" -#include "ui/views/widget/widget.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 { -// 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(); - // Hack. +2 is needed to prevent labels from getting elided like - // "abc..." in some cases. TODO(satorux): Figure out why it's - // necessary. - size.set_width(size.width() + 2); - if (size.width() < kMinCandidateLabelWidth) { - size.set_width(kMinCandidateLabelWidth); - } - if (size.width() > kMaxCandidateLabelWidth) { - size.set_width(kMaxCandidateLabelWidth); - } - return size; +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() {} - DISALLOW_COPY_AND_ASSIGN(VerticalCandidateLabel); -}; - -// Wraps the given view with some padding, and returns it. -views::View* WrapWithPadding(views::View* view, const gfx::Insets& insets) { - views::View* wrapper = new views::View; - // Use GridLayout to give some insets inside. - views::GridLayout* layout = new views::GridLayout(wrapper); - wrapper->SetLayoutManager(layout); // |wrapper| owns |layout|. - layout->SetInsets(insets); - - views::ColumnSet* column_set = layout->AddColumnSet(0); - column_set->AddColumn( - views::GridLayout::FILL, views::GridLayout::FILL, - 1, views::GridLayout::USE_PREF, 0, 0); - layout->StartRow(0, 0); - - // Add the view contents. - layout->AddView(view); // |view| is owned by |wraper|, not |layout|. - return wrapper; -} - -// Creates shortcut text from the given index and the orientation. -base::string16 CreateShortcutText(size_t index, - const CandidateWindow& candidate_window) { - if (index >= candidate_window.candidates().size()) - return base::string16(); - std::string shortcut_text = candidate_window.candidates()[index].label; - if (!shortcut_text.empty() && - candidate_window.orientation() != CandidateWindow::VERTICAL) - shortcut_text += '.'; - return base::UTF8ToUTF16(shortcut_text); -} - -// Creates the shortcut label, and returns it (never returns NULL). -// The label text is not set in this function. -views::Label* CreateShortcutLabel( - 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 == 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)); + void set_offset(int offset) { offset_ = offset; } - return shortcut_label; -} + 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())); -// Wraps the shortcut label, then decorates wrapped shortcut label -// and returns it (never returns NULL). -// The label text is not set in this function. -views::View* CreateWrappedShortcutLabel( - views::Label* shortcut_label, - CandidateWindow::Orientation orientation, - const ui::NativeTheme& theme) { - // Wrap it with padding. - const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6); - const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0); - const gfx::Insets insets = - (orientation == CandidateWindow::VERTICAL ? - kVerticalShortcutLabelInsets : - kHorizontalShortcutLabelInsets); - views::View* wrapped_shortcut_label = - WrapWithPadding(shortcut_label, insets); + // 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()); - // Add decoration based on the orientation. - if (orientation == 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); - wrapped_shortcut_label->set_background( - views::Background::CreateSolidBackground(transparent_blakish)); - shortcut_label->SetBackgroundColor( - wrapped_shortcut_label->background()->get_color()); + return bounds; } - return wrapped_shortcut_label; -} - -// Creates the candidate label, and returns it (never returns NULL). -// The label text is not set in this function. -views::Label* CreateCandidateLabel( - 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 == CandidateWindow::VERTICAL) { - candidate_label = new VerticalCandidateLabel; - } else { - candidate_label = new views::Label; + virtual gfx::Insets GetInsets() const OVERRIDE { + return gfx::Insets(); } - // 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( - 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; -} - -// Computes shortcut column size. -gfx::Size ComputeShortcutColumnSize( - const CandidateWindow& candidate_window, - const ui::NativeTheme& theme) { - int shortcut_column_width = 0; - int shortcut_column_height = 0; - // 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 = CreateShortcutLabel( - candidate_window.orientation(), theme); - scoped_ptr<views::View> wrapped_shortcut_label( - CreateWrappedShortcutLabel(shortcut_label, - candidate_window.orientation(), - theme)); - - // Compute the max width and height in shortcut labels. - // We'll create temporary shortcut labels, and choose the largest width and - // height. - for (size_t i = 0; i < candidate_window.page_size(); ++i) { - shortcut_label->SetText(CreateShortcutText(i, candidate_window)); - gfx::Size text_size = wrapped_shortcut_label->GetPreferredSize(); - shortcut_column_width = std::max(shortcut_column_width, text_size.width()); - shortcut_column_height = std::max(shortcut_column_height, - text_size.height()); - } + gfx::NativeView parent_; + int offset_; - return gfx::Size(shortcut_column_width, shortcut_column_height); -} + 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 @@ -225,467 +81,178 @@ int ComputePageIndex(const CandidateWindow& candidate_window) { return -1; } -// Computes candidate column size. -gfx::Size ComputeCandidateColumnSize( - const CandidateWindow& candidate_window) { - int candidate_column_width = 0; - int candidate_column_height = 0; - scoped_ptr<views::Label> candidate_label( - CreateCandidateLabel(candidate_window.orientation())); - - // Compute the start index of |candidate_window_|. - const int current_page_index = ComputePageIndex(candidate_window); - if (current_page_index < 0) - return gfx::Size(0, 0); - const size_t start_from = current_page_index * candidate_window.page_size(); - - // Compute the max width and height in candidate labels. - // We'll create temporary candidate labels, and choose the largest width and - // height. - for (size_t i = 0; - i + start_from < candidate_window.candidates().size(); - ++i) { - const size_t index = start_from + i; - - candidate_label->SetText( - base::UTF8ToUTF16(candidate_window.candidates()[index].value)); - gfx::Size text_size = candidate_label->GetPreferredSize(); - candidate_column_width = std::max(candidate_column_width, - text_size.width()); - candidate_column_height = std::max(candidate_column_height, - text_size.height()); - } - - return gfx::Size(candidate_column_width, candidate_column_height); -} - -// Computes annotation column size. -gfx::Size ComputeAnnotationColumnSize( - const CandidateWindow& candidate_window, const ui::NativeTheme& theme) { - int annotation_column_width = 0; - int annotation_column_height = 0; - scoped_ptr<views::Label> annotation_label( - CreateAnnotationLabel(candidate_window.orientation(), theme)); - - // Compute the start index of |candidate_window_|. - const int current_page_index = ComputePageIndex(candidate_window); - if (current_page_index < 0) - return gfx::Size(0, 0); - const size_t start_from = current_page_index * candidate_window.page_size(); - - // Compute max width and height in annotation labels. - // We'll create temporary annotation labels, and choose the largest width and - // height. - for (size_t i = 0; - i + start_from < candidate_window.candidates().size(); - ++i) { - const size_t index = start_from + i; - - annotation_label->SetText( - base::UTF8ToUTF16(candidate_window.candidates()[index].annotation)); - gfx::Size text_size = annotation_label->GetPreferredSize(); - annotation_column_width = std::max(annotation_column_width, - text_size.width()); - annotation_column_height = std::max(annotation_column_height, - text_size.height()); - } - - return gfx::Size(annotation_column_width, annotation_column_height); -} - } // namespace -// InformationTextArea is a HidableArea having a single Label in it. -class InformationTextArea : public HidableArea { +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 minWidth) - : minWidth_(minWidth) { + 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)); - const gfx::Insets kInsets(2, 2, 2, 4); - views::View* contents = WrapWithPadding(label_, kInsets); - SetContents(contents); - contents->set_border(views::Border::CreateSolidBorder( - 1, - GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_MenuBorderColor))); - contents->set_background(views::Background::CreateSolidBackground( + SetLayoutManager(new views::FillLayout()); + AddChildView(label_); + set_background(views::Background::CreateSolidBackground( color_utils::AlphaBlend(SK_ColorBLACK, GetNativeTheme()->GetSystemColor( ui::NativeTheme::kColorId_WindowBackground), 0x10))); - label_->SetBackgroundColor(contents->background()->get_color()); } - // Set the displayed text. + // 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 = HidableArea::GetPreferredSize(); - // Hack. +2 is needed as the same reason as in VerticalCandidateLabel - size.set_width(size.width() + 2); - if (size.width() < minWidth_) { - size.set_width(minWidth_); - } + gfx::Size size = views::View::GetPreferredSize(); + size.SetToMax(gfx::Size(min_width_, 0)); return size; } private: views::Label* label_; - int minWidth_; + int min_width_; DISALLOW_COPY_AND_ASSIGN(InformationTextArea); }; -CandidateView::CandidateView( - CandidateWindowView* parent_candidate_window, - int index_in_page, - CandidateWindow::Orientation orientation) - : index_in_page_(index_in_page), - orientation_(orientation), - parent_candidate_window_(parent_candidate_window), - shortcut_label_(NULL), - candidate_label_(NULL), - annotation_label_(NULL), - infolist_icon_(NULL), - infolist_icon_enabled_(false) { -} - -void CandidateView::Init(int shortcut_column_width, - int candidate_column_width, - int annotation_column_width, - int column_height) { - views::GridLayout* layout = new views::GridLayout(this); - SetLayoutManager(layout); // |this| owns |layout|. - - // Create Labels. - const ui::NativeTheme& theme = *GetNativeTheme(); - shortcut_label_ = CreateShortcutLabel(orientation_, theme); - views::View* wrapped_shortcut_label = - CreateWrappedShortcutLabel(shortcut_label_, orientation_, theme); - candidate_label_ = CreateCandidateLabel(orientation_); - annotation_label_ = CreateAnnotationLabel(orientation_, theme); - - // Initialize the column set with three columns. - views::ColumnSet* column_set = layout->AddColumnSet(0); - - // If orientation is vertical, each column width is fixed. - // Otherwise the width is resizable. - const views::GridLayout::SizeType column_type = - orientation_ == CandidateWindow::VERTICAL ? - views::GridLayout::FIXED : views::GridLayout::USE_PREF; - - const int padding_column_width = - orientation_ == CandidateWindow::VERTICAL ? 4 : 6; - - // Set shortcut column type and width. - column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, - 0, column_type, shortcut_column_width, 0); - column_set->AddPaddingColumn(0, padding_column_width); - - // Set candidate column type and width. - column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, - 1, views::GridLayout::USE_PREF, 0, - orientation_ == CandidateWindow::VERTICAL ? - candidate_column_width : 0); - column_set->AddPaddingColumn(0, padding_column_width); - - // Set annotation column type and width. - column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, - 0, column_type, annotation_column_width, 0); - - if (orientation_ == CandidateWindow::VERTICAL) { - column_set->AddPaddingColumn(0, 1); - column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, - views::GridLayout::FIXED, kInfolistIndicatorIconWidth, - 0); - column_set->AddPaddingColumn(0, 2); - } else { - column_set->AddPaddingColumn(0, padding_column_width); - } - - // Add the shortcut label, the candidate label, and annotation label. - layout->StartRow(0, 0); - // |wrapped_shortcut_label|, |candidate_label_|, and |annotation_label_| - // will be owned by |this|. - layout->AddView(wrapped_shortcut_label, - 1, // Column span. - 1, // Row span. - views::GridLayout::FILL, // Horizontal alignment. - views::GridLayout::FILL, // Vertical alignment. - -1, // Preferred width, not specified. - column_height); // Preferred height. - layout->AddView(candidate_label_, - 1, // Column span. - 1, // Row span. - views::GridLayout::FILL, // Horizontal alignment. - views::GridLayout::FILL, // Vertical alignment. - -1, // Preferred width, not specified. - column_height); // Preferred height. - layout->AddView(annotation_label_, - 1, // Column span. - 1, // Row span. - views::GridLayout::FILL, // Horizontal alignment. - views::GridLayout::FILL, // Vertical alignemnt. - -1, // Preferred width, not specified. - column_height); // Preferred height. - if (orientation_ == CandidateWindow::VERTICAL) { - infolist_icon_ = new views::View; - views::View* infolist_icon_wrapper = new views::View; - views::GridLayout* infolist_icon_layout = - new views::GridLayout(infolist_icon_wrapper); - // |infolist_icon_layout| is owned by |infolist_icon_wrapper|. - infolist_icon_wrapper->SetLayoutManager(infolist_icon_layout); - infolist_icon_layout->AddColumnSet(0)->AddColumn( - views::GridLayout::FILL, views::GridLayout::FILL, - 0, views::GridLayout::FIXED, kInfolistIndicatorIconWidth, 0); - infolist_icon_layout->AddPaddingRow(0, kInfolistIndicatorIconPadding); - infolist_icon_layout->StartRow(1.0, 0); // infolist_icon_ is resizable. - // |infolist_icon_| is owned by |infolist_icon_wrapper|. - infolist_icon_layout->AddView(infolist_icon_); - infolist_icon_layout->AddPaddingRow(0, kInfolistIndicatorIconPadding); - // |infolist_icon_wrapper| is owned by |this|. - layout->AddView(infolist_icon_wrapper); - } - UpdateLabelBackgroundColors(); -} - -void CandidateView::SetCandidateText(const base::string16& text) { - candidate_label_->SetText(text); -} - -void CandidateView::SetShortcutText(const base::string16& text) { - shortcut_label_->SetText(text); -} - -void CandidateView::SetAnnotationText(const base::string16& text) { - annotation_label_->SetText(text); -} - -void CandidateView::SetInfolistIcon(bool enable) { - if (!infolist_icon_ || (infolist_icon_enabled_ == enable)) - return; - infolist_icon_enabled_ = enable; - infolist_icon_->set_background( - enable ? - views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor)) : - NULL); - UpdateLabelBackgroundColors(); - SchedulePaint(); -} - -void CandidateView::Select() { - set_background( - views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused))); - set_border(views::Border::CreateSolidBorder( - 1, GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor))); - UpdateLabelBackgroundColors(); - // Need to call SchedulePaint() for background and border color changes. - SchedulePaint(); -} - -void CandidateView::Unselect() { - set_background(NULL); - set_border(NULL); - UpdateLabelBackgroundColors(); - SchedulePaint(); // See comments at Select(). -} - -void CandidateView::SetRowEnabled(bool enabled) { - shortcut_label_->SetEnabled(enabled); -} - -gfx::Point CandidateView::GetCandidateLabelPosition() const { - return candidate_label_->GetMirroredPosition(); -} - -bool CandidateView::OnMousePressed(const ui::MouseEvent& event) { - // TODO(kinaba): On Windows and MacOS, candidate windows typically commits a - // candidate at OnMouseReleased event. We have chosen OnMousePressed here for - // working around several obstacle rising from views implementation over GTK. - // See: http://crosbug.com/11423#c11. Since we have moved from GTK to Aura, - // the reasoning should have became obsolete. We might want to reconsider - // implementing mouse-up selection. - SelectCandidateAt(event.location()); - return false; -} - -void CandidateView::OnGestureEvent(ui::GestureEvent* event) { - if (event->type() == ui::ET_GESTURE_TAP) { - SelectCandidateAt(event->location()); - event->SetHandled(); - return; - } - View::OnGestureEvent(event); -} - -void CandidateView::SelectCandidateAt(const gfx::Point& location) { - gfx::Point location_in_candidate_window = location; - views::View::ConvertPointToTarget(this, parent_candidate_window_, - &location_in_candidate_window); - parent_candidate_window_->OnCandidatePressed(location_in_candidate_window); - parent_candidate_window_->CommitCandidate(); -} - -void CandidateView::UpdateLabelBackgroundColors() { - SkColor color = background() ? - background()->get_color() : - GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_WindowBackground); - if (orientation_ != CandidateWindow::VERTICAL) - shortcut_label_->SetBackgroundColor(color); - candidate_label_->SetBackgroundColor(color); - annotation_label_->SetBackgroundColor(color); -} - -CandidateWindowView::CandidateWindowView(views::Widget* parent_frame) +CandidateWindowView::CandidateWindowView(gfx::NativeView parent) : selected_candidate_index_in_page_(-1), - parent_frame_(parent_frame), - preedit_area_(NULL), - header_area_(NULL), - candidate_area_(NULL), - footer_area_(NULL), - previous_shortcut_column_size_(0, 0), - previous_candidate_column_size_(0, 0), - previous_annotation_column_size_(0, 0), should_show_at_composition_head_(false), should_show_upper_side_(false), was_candidate_window_open_(false) { -} - -CandidateWindowView::~CandidateWindowView() { -} + set_parent_window(parent); + set_margins(gfx::Insets()); -void CandidateWindowView::Init() { // Set the background and the border of the view. + ui::NativeTheme* theme = GetNativeTheme(); set_background( - views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor( + views::Background::CreateSolidBackground(theme->GetSystemColor( ui::NativeTheme::kColorId_WindowBackground))); set_border(views::Border::CreateSolidBorder( - 1, GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_MenuBorderColor))); - - // Create areas. - preedit_area_ = new InformationTextArea(gfx::ALIGN_LEFT, - kMinPreeditAreaWidth); - header_area_ = new InformationTextArea(gfx::ALIGN_LEFT, 0); - candidate_area_ = new HidableArea; - candidate_area_->SetContents(new views::View); - footer_area_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0); - - // Set the window layout of the view - views::GridLayout* layout = new views::GridLayout(this); - SetLayoutManager(layout); // |this| owns |layout|. - views::ColumnSet* column_set = layout->AddColumnSet(0); - column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, - 0, views::GridLayout::USE_PREF, 0, 0); - - // Add the preedit area - layout->StartRow(0, 0); - layout->AddView(preedit_area_); // |preedit_area_| is owned by |this|. + 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() == 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)); + } +} - // Add the header area. - layout->StartRow(0, 0); - layout->AddView(header_area_); // |header_area_| is owned by |this|. +CandidateWindowView::~CandidateWindowView() { +} - // Add the candidate area. - layout->StartRow(0, 0); - layout->AddView(candidate_area_); // |candidate_area_| is owned by |this|. +views::Widget* CandidateWindowView::InitWidget() { + views::Widget* widget = BubbleDelegateView::CreateBubble(this); - // Add the footer area. - layout->StartRow(0, 0); - layout->AddView(footer_area_); // |footer_area_| is owned by |this|. -} + views::corewm::SetWindowVisibilityAnimationType( + widget->GetNativeView(), + views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); -void CandidateWindowView::HideAll() { - parent_frame_->Hide(); - NotifyIfCandidateWindowOpenedOrClosed(); + GetBubbleFrameView()->SetBubbleBorder( + new CandidateWindowBorder(parent_window())); + return widget; } -void CandidateWindowView::UpdateParentArea() { - if (candidate_area_->IsShown() || - header_area_->IsShown() || - footer_area_->IsShown() || - preedit_area_->IsShown()) { - ResizeAndMoveParentFrame(); - parent_frame_->Show(); +void CandidateWindowView::UpdateVisibility() { + if (candidate_area_->visible() || auxiliary_text_->visible() || + preedit_->visible()) { + SizeToContents(); } else { - parent_frame_->Hide(); + GetWidget()->Close(); } - NotifyIfCandidateWindowOpenedOrClosed(); } void CandidateWindowView::HideLookupTable() { - candidate_area_->Hide(); - header_area_->Hide(); - footer_area_->Hide(); - UpdateParentArea(); + candidate_area_->SetVisible(false); + UpdateVisibility(); } void CandidateWindowView::HidePreeditText() { - preedit_area_->Hide(); - UpdateParentArea(); + preedit_->SetVisible(false); + UpdateVisibility(); } void CandidateWindowView::ShowPreeditText() { - preedit_area_->Show(); - UpdateParentArea(); + preedit_->SetVisible(true); + UpdateVisibility(); } void CandidateWindowView::UpdatePreeditText(const std::string& utf8_text) { - preedit_area_->SetText(utf8_text); + preedit_->SetText(utf8_text); } void CandidateWindowView::ShowLookupTable() { - if (!candidate_area_->IsShown()) - should_show_upper_side_ = false; - candidate_area_->Show(); - - // Show auxiliary text. - if (!candidate_window_.is_auxiliary_text_visible()) { - header_area_->Hide(); - footer_area_->Hide(); - } else if (candidate_window_.orientation() == CandidateWindow::HORIZONTAL) { - header_area_->Show(); - footer_area_->Hide(); - } else { - header_area_->Hide(); - footer_area_->Show(); - } - - UpdateParentArea(); -} - -void CandidateWindowView::NotifyIfCandidateWindowOpenedOrClosed() { - bool is_open = IsCandidateWindowOpen(); - if (!was_candidate_window_open_ && is_open) { - FOR_EACH_OBSERVER(Observer, observers_, OnCandidateWindowOpened()); - } else if (was_candidate_window_open_ && !is_open) { - FOR_EACH_OBSERVER(Observer, observers_, OnCandidateWindowClosed()); - } - was_candidate_window_open_ = is_open; -} - -bool CandidateWindowView::ShouldUpdateCandidateViews( - const CandidateWindow& old_candidate_window, - const CandidateWindow& new_candidate_window) { - return !old_candidate_window.IsEqual(new_candidate_window); + candidate_area_->SetVisible(true); + auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible()); + UpdateVisibility(); } void CandidateWindowView::UpdateCandidates( const CandidateWindow& new_candidate_window) { - const bool should_update = ShouldUpdateCandidateViews(candidate_window_, - new_candidate_window); // Updating the candidate views is expensive. We'll skip this if possible. - if (should_update) { + 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() == 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); @@ -693,57 +260,52 @@ void CandidateWindowView::UpdateCandidates( = 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) { + 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(); - // In some cases, engines send empty shortcut labels. For instance, - // ibus-mozc sends empty labels when they show suggestions. In this - // case, we should not show shortcut labels. - bool no_shortcut_mode = true; - for (size_t i = 0; i < new_candidate_window.candidates().size(); ++i) { - if (!new_candidate_window.candidates()[i].label.empty()) { - no_shortcut_mode = false; - break; - } - } - + 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 shortcut text. - if (no_shortcut_mode) { - candidate_view->SetShortcutText(base::string16()); - } else { - // At this moment, we don't use labels sent from engines for UX - // reasons. First, we want to show shortcut labels in empty rows - // (ex. show 6, 7, 8, ... in empty rows when the number of - // candidates is 5). Second, we want to add a period after each - // shortcut label when the candidate window is horizontal. - candidate_view->SetShortcutText( - CreateShortcutText(i, new_candidate_window)); - } // Set the candidate text. - if (candidate_index < new_candidate_window.candidates().size()) { - const CandidateWindow::Entry& entry = - new_candidate_window.candidates()[candidate_index]; - candidate_view->SetCandidateText(base::UTF8ToUTF16(entry.value)); - candidate_view->SetAnnotationText(base::UTF8ToUTF16(entry.annotation)); - candidate_view->SetRowEnabled(true); - candidate_view->SetInfolistIcon(!entry.description_title.empty()); + if (candidate_index < new_candidate_window.candidates().size()) { + const 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->SetCandidateText(base::string16()); - candidate_view->SetAnnotationText(base::string16()); - candidate_view->SetRowEnabled(false); + candidate_view->SetEntry(CandidateWindow::Entry()); + candidate_view->SetState(views::Button::STATE_DISABLED); candidate_view->SetInfolistIcon(false); } + if (new_candidate_window.orientation() == 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() == 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<CandidateWindowBorder*>( + GetBubbleFrameView()->bubble_border()); + if (new_candidate_window.orientation() == 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_. @@ -761,142 +323,44 @@ void CandidateWindowView::UpdateCandidates( if (0 <= selected_candidate_index_in_page_ && static_cast<size_t>(selected_candidate_index_in_page_) < candidate_views_.size()) { - candidate_views_[selected_candidate_index_in_page_]->Unselect(); + candidate_views_[selected_candidate_index_in_page_]->SetState( + views::Button::STATE_NORMAL); selected_candidate_index_in_page_ = -1; } } // Updates auxiliary text - if (candidate_window_.is_auxiliary_text_visible()) { - header_area_->SetText(candidate_window_.auxiliary_text()); - footer_area_->SetText(candidate_window_.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 CandidateWindow& candidate_window) { const CandidateWindow::Orientation orientation = candidate_window.orientation(); - const int page_size = candidate_window.page_size(); - views::View* candidate_area_contents = candidate_area_->contents(); - - // Current column width. - gfx::Size shortcut_column_size(0, 0); - gfx::Size candidate_column_size(0,0); - gfx::Size annotation_column_size(0, 0); - - // If orientation is horizontal, don't need to compute width, - // because each label is left aligned. - if (orientation == CandidateWindow::VERTICAL) { - const ui::NativeTheme& theme = *GetNativeTheme(); - shortcut_column_size = ComputeShortcutColumnSize(candidate_window, theme); - candidate_column_size = ComputeCandidateColumnSize(candidate_window); - annotation_column_size = ComputeAnnotationColumnSize(candidate_window, - theme); - } - - // If the requested number of views matches the number of current views, and - // previous and current column width are same, just reuse these. - // - // Note that the early exit logic is not only useful for improving - // performance, but also necessary for the horizontal candidate window - // to be redrawn properly. If we get rid of the logic, the horizontal - // candidate window won't get redrawn properly for some reason when - // there is no size change. You can test this by removing "return" here - // and type "ni" with Pinyin input method. - if (static_cast<int>(candidate_views_.size()) == page_size && - candidate_window_.orientation() == orientation && - previous_shortcut_column_size_ == shortcut_column_size && - previous_candidate_column_size_ == candidate_column_size && - previous_annotation_column_size_ == annotation_column_size) { - return; - } - - // Update the previous column widths. - previous_shortcut_column_size_ = shortcut_column_size; - previous_candidate_column_size_ = candidate_column_size; - previous_annotation_column_size_ = annotation_column_size; - - // Clear the existing candidate_views if any. - for (size_t i = 0; i < candidate_views_.size(); ++i) { - candidate_area_contents->RemoveChildView(candidate_views_[i]); - // Delete the view after getting out the current message loop iteration. - base::MessageLoop::current()->DeleteSoon(FROM_HERE, candidate_views_[i]); - } - candidate_views_.clear(); - selected_candidate_index_in_page_ = -1; // Invalidates the index. - - views::GridLayout* layout = new views::GridLayout(candidate_area_contents); - // |candidate_area_contents| owns |layout|. - candidate_area_contents->SetLayoutManager(layout); - // Initialize the column set. - views::ColumnSet* column_set = layout->AddColumnSet(0); - if (orientation == CandidateWindow::VERTICAL) { - column_set->AddColumn(views::GridLayout::FILL, - views::GridLayout::FILL, - 1, views::GridLayout::USE_PREF, 0, 0); - } else { - for (int i = 0; i < page_size; ++i) { - column_set->AddColumn(views::GridLayout::FILL, - views::GridLayout::FILL, - 0, views::GridLayout::USE_PREF, 0, 0); - } - } + const size_t page_size = candidate_window.page_size(); - // Set insets so the border of the selected candidate is drawn inside of - // the border of the main candidate window, but we don't have the inset - // at the top and the bottom as we have the borders of the header and - // footer areas. - const gfx::Insets kCandidateAreaInsets(0, 1, 0, 1); - layout->SetInsets(kCandidateAreaInsets.top(), - kCandidateAreaInsets.left(), - kCandidateAreaInsets.bottom(), - kCandidateAreaInsets.right()); + // Reset all candidate_views_ when orientation changes. + if (orientation != candidate_window_.orientation()) + STLDeleteElements(&candidate_views_); - // Use maximum height for all rows in candidate area. - const int kColumnHeight = std::max(shortcut_column_size.height(), - std::max(candidate_column_size.height(), - annotation_column_size.height())); - - // Add views to the candidate area. - if (orientation == CandidateWindow::HORIZONTAL) { - layout->StartRow(0, 0); + while (page_size < candidate_views_.size()) { + delete candidate_views_.back(); + candidate_views_.pop_back(); } - - for (int i = 0; i < page_size; ++i) { - CandidateView* candidate_row = new CandidateView(this, i, orientation); - candidate_row->Init(shortcut_column_size.width(), - candidate_column_size.width(), - annotation_column_size.width(), - kColumnHeight); - candidate_views_.push_back(candidate_row); - if (orientation == CandidateWindow::VERTICAL) { - layout->StartRow(0, 0); - } - // |candidate_row| will be owned by |candidate_area_contents|. - layout->AddView(candidate_row, - 1, // Column span. - 1, // Row span. - // Horizontal alignment. - orientation == CandidateWindow::VERTICAL ? - views::GridLayout::FILL : views::GridLayout::CENTER, - views::GridLayout::CENTER, // Vertical alignment. - -1, // Preferred width, not specified. - kColumnHeight); // Preferred height. + while (page_size > candidate_views_.size()) { + CandidateView* new_candidate = new CandidateView(this, orientation); + candidate_area_->AddChildView(new_candidate); + candidate_views_.push_back(new_candidate); } - - // Compute views size in |layout|. - // If we don't call this function, GetHorizontalOffset() often - // returns invalid value (returns 0), then candidate window - // moves right from the correct position in ResizeAndMoveParentFrame(). - // TODO(nhiroki): Figure out why it returns invalid value. - // It seems that the x-position of the candidate labels is not set. - layout->Layout(candidate_area_contents); -} - -bool CandidateWindowView::IsCandidateWindowOpen() const { - return !should_show_at_composition_head_ && - candidate_area_->visible() && candidate_area_->IsShown(); } void CandidateWindowView::SelectCandidateAt(int index_in_page) { @@ -914,123 +378,25 @@ void CandidateWindowView::SelectCandidateAt(int index_in_page) { return; } - // Unselect the currently selected candidate. - if (0 <= selected_candidate_index_in_page_ && - static_cast<size_t>(selected_candidate_index_in_page_) < - candidate_views_.size()) { - candidate_views_[selected_candidate_index_in_page_]->Unselect(); - } // 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]->Select(); + 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::OnCandidatePressed( - const gfx::Point& location) { +void CandidateWindowView::ButtonPressed(views::Button* sender, + const ui::Event& event) { for (size_t i = 0; i < candidate_views_.size(); ++i) { - gfx::Point converted_location = location; - views::View::ConvertPointToTarget(this, candidate_views_[i], - &converted_location); - if (candidate_views_[i]->HitTestPoint(converted_location)) { - SelectCandidateAt(i); - break; + if (sender == candidate_views_[i]) { + FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i)); + return; } } } -void CandidateWindowView::CommitCandidate() { - if (!(0 <= selected_candidate_index_in_page_ && - static_cast<size_t>(selected_candidate_index_in_page_) < - candidate_views_.size())) { - return; // Out of range, do nothing. - } - - FOR_EACH_OBSERVER(Observer, observers_, - OnCandidateCommitted(selected_candidate_index_in_page_)); -} - -void CandidateWindowView::ResizeAndMoveParentFrame() { - // If rendering operation comes from mozc-engine, uses mozc specific bounds, - // otherwise candidate window is shown under the cursor. - const int x = should_show_at_composition_head_? - composition_head_bounds_.x() : cursor_bounds_.x(); - // To avoid candidate-window overlapping, uses maximum y-position of mozc - // specific bounds and cursor bounds, because mozc-engine does not - // consider about multi-line composition. - const int y = should_show_at_composition_head_? - std::max(composition_head_bounds_.y(), cursor_bounds_.y()) : - cursor_bounds_.y(); - const int height = cursor_bounds_.height(); - const int horizontal_offset = GetHorizontalOffset(); - - gfx::Rect old_bounds = parent_frame_->GetClientAreaBoundsInScreen(); - gfx::Rect screen_bounds = ash::Shell::GetScreen()->GetDisplayMatching( - cursor_bounds_).work_area(); - // The size. - gfx::Rect frame_bounds = old_bounds; - frame_bounds.set_size(GetPreferredSize()); - - // The default position. - frame_bounds.set_x(x + horizontal_offset); - frame_bounds.set_y(y + height); - - // Handle overflow at the left and the top. - frame_bounds.set_x(std::max(frame_bounds.x(), screen_bounds.x())); - frame_bounds.set_y(std::max(frame_bounds.y(), screen_bounds.y())); - - // Handle overflow at the right. - const int right_overflow = frame_bounds.right() - screen_bounds.right(); - if (right_overflow > 0) { - frame_bounds.set_x(frame_bounds.x() - right_overflow); - } - - // Handle overflow at the bottom. - const int bottom_overflow = frame_bounds.bottom() - screen_bounds.bottom(); - - // To avoid flickering window position, the candidate window should be shown - // on upper side of composition string if it was shown there. - if (should_show_upper_side_ || bottom_overflow > 0) { - frame_bounds.set_y(frame_bounds.y() - height - frame_bounds.height()); - should_show_upper_side_ = true; - } - - // TODO(nona): check top_overflow here. - - // Move the window per the cursor bounds. - // SetBounds() is not cheap. Only call this when it is really changed. - if (frame_bounds != old_bounds) - parent_frame_->SetBounds(frame_bounds); -} - -int CandidateWindowView::GetHorizontalOffset() { - // Compute the horizontal offset if the candidate window is vertical. - if (!candidate_views_.empty() && - candidate_window_.orientation() == CandidateWindow::VERTICAL) { - return - candidate_views_[0]->GetCandidateLabelPosition().x(); - } - return 0; -} - -void CandidateWindowView::VisibilityChanged(View* starting_from, - bool is_visible) { - if (is_visible) { - // If the visibility of candidate window is changed, - // we should move the frame to the right position. - ResizeAndMoveParentFrame(); - } -} - -void CandidateWindowView::OnBoundsChanged(const gfx::Rect& previous_bounds) { - // If the bounds(size) of candidate window is changed, - // we should move the frame to the right position. - View::OnBoundsChanged(previous_bounds); - ResizeAndMoveParentFrame(); -} - } // 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 index dc69f75..f5848f1 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_view.h +++ b/chrome/browser/chromeos/input_method/candidate_window_view.h @@ -5,25 +5,19 @@ #ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_VIEW_H_ #define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_CANDIDATE_WINDOW_VIEW_H_ -#include "base/gtest_prod_util.h" -#include "base/memory/scoped_ptr.h" #include "chromeos/ime/candidate_window.h" -#include "ui/views/view.h" - -namespace gfx { -class Font; -} +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/controls/button/button.h" namespace chromeos { namespace input_method { class CandidateView; -class CandidateWindow; -class HidableArea; class InformationTextArea; // CandidateWindowView is the main container of the candidate window UI. -class CandidateWindowView : public views::View { +class CandidateWindowView : public views::BubbleDelegateView, + public views::ButtonListener { public: // The object can be monitored by the observer. class Observer { @@ -31,14 +25,11 @@ class CandidateWindowView : public views::View { virtual ~Observer() {} // The function is called when a candidate is committed. virtual void OnCandidateCommitted(int index) = 0; - - virtual void OnCandidateWindowOpened() = 0; - virtual void OnCandidateWindowClosed() = 0; }; - explicit CandidateWindowView(views::Widget* parent_frame); + explicit CandidateWindowView(gfx::NativeView parent); virtual ~CandidateWindowView(); - void Init(); + views::Widget* InitWidget(); // Adds the given observer. The ownership is not transferred. void AddObserver(Observer* observer) { @@ -50,19 +41,6 @@ class CandidateWindowView : public views::View { observers_.RemoveObserver(observer); } - // Selects the candidate specified by the index in the current page - // (zero-origin). Changes the appearance of the selected candidate, - // updates the information in the candidate window as needed. - void SelectCandidateAt(int index_in_page); - - // The function is called when a candidate is being dragged. From the - // given point, locates the candidate under the mouse cursor, and - // selects it. - void OnCandidatePressed(const gfx::Point& point); - - // Commits the candidate currently being selected. - void CommitCandidate(); - // Hides the lookup table. void HideLookupTable(); @@ -72,9 +50,6 @@ class CandidateWindowView : public views::View { // Hides the preedit text. void HidePreeditText(); - // Hides whole the candidate window. - void HideAll(); - // Shows the lookup table. void ShowLookupTable(); @@ -87,83 +62,27 @@ class CandidateWindowView : public views::View { // Updates the preedit text. void UpdatePreeditText(const std::string& utf8_text); - // Returns true if we should update candidate views in the window. For - // instance, if we are going to show the same candidates as before, we - // don't have to update candidate views. This happens when the user just - // moves the cursor in the same page in the candidate window. - static bool ShouldUpdateCandidateViews( - const CandidateWindow& old_candidate_window, - const CandidateWindow& new_candidate_window); - // Updates candidates of the candidate window from |candidate_window|. // Candidates are arranged per |orientation|. void UpdateCandidates(const CandidateWindow& candidate_window); - // Resizes and moves the parent frame. The two actions should be - // performed consecutively as resizing may require the candidate window - // to move. For instance, we may need to move the candidate window from - // below the cursor to above the cursor, if the candidate window becomes - // too big to be shown near the bottom of the screen. This function - // needs to be called when the visible contents of the candidate window - // are modified. - void ResizeAndMoveParentFrame(); - - // Returns the horizontal offset used for placing the vertical candidate - // window so that the first candidate is aligned with the the text being - // converted like: - // - // XXX <- The user is converting XXX - // +-----+ - // |1 XXX| - // |2 YYY| - // |3 ZZZ| - // - // Returns 0 if no candidate is present. - int GetHorizontalOffset(); - - void set_cursor_bounds(const gfx::Rect& cursor_bounds) { - cursor_bounds_ = cursor_bounds; - } - - void set_composition_head_bounds( - const gfx::Rect& composition_head_bounds) { - composition_head_bounds_ = composition_head_bounds; - } + void SetCursorBounds(const gfx::Rect& cursor_bounds, + const gfx::Rect& composition_head); - const gfx::Rect& cursor_bounds() const { return cursor_bounds_; } - const gfx::Rect& composition_head_bounds() const { - return composition_head_bounds_; - } - - protected: - // Override View::VisibilityChanged() - virtual void VisibilityChanged(View* starting_from, bool is_visible) OVERRIDE; + private: + friend class CandidateWindowViewTest; - // Override View::OnBoundsChanged() - virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE; + // Overridden from views::ButtonListener: + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; - private: - FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, - UpdateCandidatesTest_CursorVisibility); - FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, ShortcutSettingTest); - FRIEND_TEST_ALL_PREFIXES(CandidateWindowViewTest, - DoNotChangeRowHeightWithLabelSwitchTest); + void SelectCandidateAt(int index_in_page); + void UpdateVisibility(); // Initializes the candidate views if needed. void MaybeInitializeCandidateViews(const CandidateWindow& candidate_window); - // Returns the appropriate area (header or footer) to put auxiliary texts. - InformationTextArea* GetAuxiliaryTextArea(); - - // Returns true if the candidate window is open. The suggestion window does - // not count as the candidate window. - bool IsCandidateWindowOpen() const; - - // Notifies observers if the candidate window's opened/closed state has - // changed from the previous call to this function. - void NotifyIfCandidateWindowOpenedOrClosed(); - - // The candidate window. + // The candidate window data model. CandidateWindow candidate_window_; // The index in the current page of the candidate currently being selected. @@ -172,28 +91,14 @@ class CandidateWindowView : public views::View { // The observers of the object. ObserverList<Observer> observers_; - // The parent frame. - views::Widget* parent_frame_; - // 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 preedit area is where the preedit text is shown, if it is needed - // in cases such as the focus is on a plugin that doesn't support in-line - // preedit drawing. - InformationTextArea* preedit_area_; - // The header area is where the auxiliary text is shown, if the - // orientation is horizontal. If the auxiliary text is not provided, we - // show nothing. For instance, we show pinyin text like "zhong'guo". - InformationTextArea* header_area_; - // The candidate area is where candidates are rendered. - HidableArea* candidate_area_; // The candidate views are used for rendering candidates. std::vector<CandidateView*> candidate_views_; - // The footer area is where the auxiliary text is shown, if the - // orientation is vertical. Usually the auxiliary text is used for - // showing candidate number information like 2/19. - InformationTextArea* footer_area_; // Current columns size in |candidate_area_|. gfx::Size previous_shortcut_column_size_; @@ -218,10 +123,6 @@ class CandidateWindowView : public views::View { // send OnCandidateWindowOpened and OnCandidateWindowClosed events. bool was_candidate_window_open_; - // This function judge whether the candidate window should be shown or not, - // if should be, shows parent_frame and if not, hides parent_frame. - void UpdateParentArea(); - DISALLOW_COPY_AND_ASSIGN(CandidateWindowView); }; diff --git a/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc b/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc index dbd8915..f63d7d6 100644 --- a/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc +++ b/chrome/browser/chromeos/input_method/candidate_window_view_unittest.cc @@ -63,7 +63,41 @@ void InitCandidateWindowWithCandidatesFilled( } // 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 CandidateWindow& candidate_window) { + candidate_window_view_->MaybeInitializeCandidateViews(candidate_window); + } + void ExpectLabels(const std::string& shortcut, const std::string& candidate, const std::string& annotation, @@ -72,61 +106,50 @@ class CandidateWindowViewTest : public views::ViewsTestBase { EXPECT_EQ(candidate, base::UTF16ToUTF8(row->candidate_label_->text())); EXPECT_EQ(annotation, base::UTF16ToUTF8(row->annotation_label_->text())); } -}; -TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility) { - views::Widget* widget = new views::Widget; - views::Widget::InitParams params = - CreateParams(views::Widget::InitParams::TYPE_WINDOW); - widget->Init(params); + private: + // owned by |parent_|. + CandidateWindowView* candidate_window_view_; - CandidateWindowView candidate_window_view(widget); - candidate_window_view.Init(); + DISALLOW_COPY_AND_ASSIGN(CandidateWindowViewTest); +}; +TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility) { // Visible (by default) cursor. CandidateWindow candidate_window; const int candidate_window_size = 9; InitCandidateWindowWithCandidatesFilled(candidate_window_size, &candidate_window); - candidate_window_view.UpdateCandidates(candidate_window); - EXPECT_EQ(0, candidate_window_view.selected_candidate_index_in_page_); + 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, candidate_window_view.selected_candidate_index_in_page_); + 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, candidate_window_view.selected_candidate_index_in_page_); + 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, - candidate_window_view.selected_candidate_index_in_page_); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(candidate_window_size - 1, selected_candidate_index_in_page()); } TEST_F(CandidateWindowViewTest, SelectCandidateAtTest) { - views::Widget* widget = new views::Widget; - views::Widget::InitParams params = - CreateParams(views::Widget::InitParams::TYPE_WINDOW); - widget->Init(params); - - CandidateWindowView candidate_window_view(widget); - candidate_window_view.Init(); - // Set 9 candidates. 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); + candidate_window_view()->UpdateCandidates(candidate_window_large); // Select the last candidate. - candidate_window_view.SelectCandidateAt(candidate_window_large_size - 1); + SelectCandidateAt(candidate_window_large_size - 1); // Reduce the number of candidates to 3. CandidateWindow candidate_window_small; @@ -136,8 +159,8 @@ TEST_F(CandidateWindowViewTest, SelectCandidateAtTest) { 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); - candidate_window_view.SelectCandidateAt(candidate_window_small_size - 1); + candidate_window_view()->UpdateCandidates(candidate_window_small); + SelectCandidateAt(candidate_window_small_size - 1); } TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { @@ -145,22 +168,14 @@ TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { const char* kCustomizedLabel[] = { "a", "s", "d" }; const char* kExpectedHorizontalCustomizedLabel[] = { "a.", "s.", "d." }; - views::Widget* widget = new views::Widget; - views::Widget::InitParams params = - CreateParams(views::Widget::InitParams::TYPE_WINDOW); - widget->Init(params); - - CandidateWindowView candidate_window_view(widget); - candidate_window_view.Init(); - { SCOPED_TRACE("candidate_views allocation test"); const size_t kMaxPageSize = 16; for (size_t i = 1; i < kMaxPageSize; ++i) { CandidateWindow candidate_window; InitCandidateWindow(i, &candidate_window); - candidate_window_view.UpdateCandidates(candidate_window); - EXPECT_EQ(i, candidate_window_view.candidate_views_.size()); + candidate_window_view()->UpdateCandidates(candidate_window); + EXPECT_EQ(i, GetCandidatesSize()); } } { @@ -180,12 +195,12 @@ TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { candidate_window.mutable_candidates()->push_back(entry); } - candidate_window_view.UpdateCandidates(candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); - ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); + ASSERT_EQ(kPageSize, GetCandidatesSize()); for (size_t i = 0; i < kPageSize; ++i) { ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], - candidate_window_view.candidate_views_[i]); + GetCandidateAt(i)); } } { @@ -206,13 +221,13 @@ TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { candidate_window.mutable_candidates()->push_back(entry); } - candidate_window_view.UpdateCandidates(candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); - ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); + ASSERT_EQ(kPageSize, GetCandidatesSize()); // Confirm actual labels not containing ".". for (size_t i = 0; i < kPageSize; ++i) { ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], - candidate_window_view.candidate_views_[i]); + GetCandidateAt(i)); } } { @@ -232,15 +247,15 @@ TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { candidate_window.mutable_candidates()->push_back(entry); } - candidate_window_view.UpdateCandidates(candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); - ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); + ASSERT_EQ(kPageSize, GetCandidatesSize()); // Confirm actual labels not containing ".". for (size_t i = 0; i < kPageSize; ++i) { ExpectLabels(kCustomizedLabel[i], kSampleCandidate[i], kSampleAnnotation[i], - candidate_window_view.candidate_views_[i]); + GetCandidateAt(i)); } } { @@ -260,20 +275,17 @@ TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { candidate_window.mutable_candidates()->push_back(entry); } - candidate_window_view.UpdateCandidates(candidate_window); + candidate_window_view()->UpdateCandidates(candidate_window); - ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); + ASSERT_EQ(kPageSize, GetCandidatesSize()); // Confirm actual labels not containing ".". for (size_t i = 0; i < kPageSize; ++i) { ExpectLabels(kExpectedHorizontalCustomizedLabel[i], kSampleCandidate[i], kSampleAnnotation[i], - candidate_window_view.candidate_views_[i]); + GetCandidateAt(i)); } } - - // We should call CloseNow method, otherwise this test will leak memory. - widget->CloseNow(); } TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { @@ -293,17 +305,6 @@ TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { const char kSampleAnnotation2[] = "\xE3\x81\x82"; // multi byte string. const char kSampleAnnotation3[] = "......"; - // For testing, we have to prepare empty widget. - // We should NOT manually free widget by default, otherwise double free will - // be occurred. So, we should instantiate widget class with "new" operation. - views::Widget* widget = new views::Widget; - views::Widget::InitParams params = - CreateParams(views::Widget::InitParams::TYPE_WINDOW); - widget->Init(params); - - CandidateWindowView candidate_window_view(widget); - candidate_window_view.Init(); - // Create CandidateWindow object. InitCandidateWindow(kPageSize, &candidate_window); @@ -336,64 +337,46 @@ TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { // Test for shortcut mode to no-shortcut mode. // Initialize with a shortcut mode candidate window. - candidate_window_view.MaybeInitializeCandidateViews(candidate_window); - ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); + MaybeInitializeCandidateViews(candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); // Check the selected index is invalidated. - EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); + EXPECT_EQ(-1, selected_candidate_index_in_page()); before_height = - candidate_window_view.candidate_views_[0]->GetContentsBounds().height(); + GetCandidateAt(0)->GetContentsBounds().height(); // Checks all entry have same row height. - for (size_t i = 1; i < candidate_window_view.candidate_views_.size(); ++i) { - const CandidateView* view = candidate_window_view.candidate_views_[i]; - EXPECT_EQ(before_height, view->GetContentsBounds().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. - candidate_window_view.MaybeInitializeCandidateViews( - no_shortcut_candidate_window); - ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); + MaybeInitializeCandidateViews(no_shortcut_candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); // Check the selected index is invalidated. - EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); - EXPECT_EQ(before_height, - candidate_window_view.candidate_views_[0]->GetContentsBounds() - .height()); + 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 < candidate_window_view.candidate_views_.size(); ++i) { - const CandidateView* view = candidate_window_view.candidate_views_[i]; - EXPECT_EQ(before_height, view->GetContentsBounds().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. - candidate_window_view.MaybeInitializeCandidateViews( - no_shortcut_candidate_window); - ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); + MaybeInitializeCandidateViews(no_shortcut_candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); // Check the selected index is invalidated. - EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); - before_height = - candidate_window_view.candidate_views_[0]->GetContentsBounds().height(); + 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 < candidate_window_view.candidate_views_.size(); ++i) { - const CandidateView* view = candidate_window_view.candidate_views_[i]; - EXPECT_EQ(before_height, view->GetContentsBounds().height()); - } + for (size_t i = 1; i < GetCandidatesSize(); ++i) + EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height()); // Initialize with a shortcut mode candidate window. - candidate_window_view.MaybeInitializeCandidateViews(candidate_window); - ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); + MaybeInitializeCandidateViews(candidate_window); + ASSERT_EQ(3UL, GetCandidatesSize()); // Check the selected index is invalidated. - EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); - EXPECT_EQ(before_height, - candidate_window_view.candidate_views_[0]->GetContentsBounds() - .height()); + 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 < candidate_window_view.candidate_views_.size(); ++i) { - const CandidateView* view = candidate_window_view.candidate_views_[i]; - EXPECT_EQ(before_height, view->GetContentsBounds().height()); - } - - // We should call CloseNow method, otherwise this test will leak memory. - widget->CloseNow(); + 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/hidable_area.cc b/chrome/browser/chromeos/input_method/hidable_area.cc deleted file mode 100644 index 9c396c7d..0000000 --- a/chrome/browser/chromeos/input_method/hidable_area.cc +++ /dev/null @@ -1,48 +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/hidable_area.h" - -#include "ui/views/layout/fill_layout.h" - -namespace chromeos { -namespace input_method { - -HidableArea::HidableArea() { - place_holder_.reset(new views::View); - place_holder_->set_owned_by_client(); // Won't own - - // Initially show nothing. - SetLayoutManager(new views::FillLayout); - AddChildView(place_holder_.get()); -} - -HidableArea::~HidableArea() { -} - -void HidableArea::SetContents(views::View* contents) { - contents_.reset(contents); - contents_->set_owned_by_client(); // Won't own -} - -void HidableArea::Show() { - if (contents_.get() && contents_->parent() != this) { - RemoveAllChildViews(false); // Don't delete child views. - AddChildView(contents_.get()); - } -} - -void HidableArea::Hide() { - if (IsShown()) { - RemoveAllChildViews(false); // Don't delete child views. - AddChildView(place_holder_.get()); - } -} - -bool HidableArea::IsShown() const { - return contents_.get() && contents_->parent() == this; -} - -} // namespace input_method -} // namespace chromeos diff --git a/chrome/browser/chromeos/input_method/hidable_area.h b/chrome/browser/chromeos/input_method/hidable_area.h deleted file mode 100644 index 76f468a..0000000 --- a/chrome/browser/chromeos/input_method/hidable_area.h +++ /dev/null @@ -1,48 +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_HIDABLE_AREA_H_ -#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_HIDABLE_AREA_H_ - -#include "base/memory/scoped_ptr.h" -#include "ui/views/widget/widget.h" - -namespace chromeos { -namespace input_method { - -// HidableArea is used as an area to place optional information that can be -// turned displaying off if it is unnecessary. -class HidableArea : public views::View { - public: - HidableArea(); - virtual ~HidableArea(); - - // Sets the content view. - void SetContents(views::View* contents); - - // Shows the content. - void Show(); - - // Hides the content. - void Hide(); - - // Returns whether the content is already set and shown. - bool IsShown() const; - - // Returns the content. - views::View* contents() { - return contents_.get(); - } - - private: - scoped_ptr<views::View> contents_; - scoped_ptr<views::View> place_holder_; - - DISALLOW_COPY_AND_ASSIGN(HidableArea); -}; - -} // namespace input_method -} // namespace chromeos - -#endif // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_HIDABLE_AREA_H_ diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index d68c055..feed926 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -406,14 +406,14 @@ '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/hidable_area.cc', - 'browser/chromeos/input_method/hidable_area.h', 'browser/chromeos/input_method/infolist_window.cc', 'browser/chromeos/input_method/infolist_window.h', 'browser/chromeos/input_method/input_method_engine.cc', |