1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/ime/candidate_view.h"
6
7#include "ash/ime/candidate_window_constants.h"
8#include "base/strings/utf_string_conversions.h"
9#include "ui/base/ime/candidate_window.h"
10#include "ui/gfx/color_utils.h"
11#include "ui/native_theme/native_theme.h"
12#include "ui/views/background.h"
13#include "ui/views/border.h"
14#include "ui/views/controls/label.h"
15#include "ui/views/widget/widget.h"
16
17namespace ash {
18namespace ime {
19
20namespace {
21
22// VerticalCandidateLabel is used for rendering candidate text in
23// the vertical candidate window.
24class VerticalCandidateLabel : public views::Label {
25 public:
26  VerticalCandidateLabel() {}
27
28 private:
29  virtual ~VerticalCandidateLabel() {}
30
31  // Returns the preferred size, but guarantees that the width has at
32  // least kMinCandidateLabelWidth pixels.
33  virtual gfx::Size GetPreferredSize() const OVERRIDE {
34    gfx::Size size = Label::GetPreferredSize();
35    size.SetToMax(gfx::Size(kMinCandidateLabelWidth, 0));
36    size.SetToMin(gfx::Size(kMaxCandidateLabelWidth, size.height()));
37    return size;
38  }
39
40  DISALLOW_COPY_AND_ASSIGN(VerticalCandidateLabel);
41};
42
43// Creates the shortcut label, and returns it (never returns NULL).
44// The label text is not set in this function.
45views::Label* CreateShortcutLabel(
46    ui::CandidateWindow::Orientation orientation,
47    const ui::NativeTheme& theme) {
48  // Create the shortcut label. The label will be owned by
49  // |wrapped_shortcut_label|, hence it's deleted when
50  // |wrapped_shortcut_label| is deleted.
51  views::Label* shortcut_label = new views::Label;
52
53  if (orientation == ui::CandidateWindow::VERTICAL) {
54    shortcut_label->SetFontList(
55        shortcut_label->font_list().Derive(kFontSizeDelta, gfx::Font::BOLD));
56  } else {
57    shortcut_label->SetFontList(
58        shortcut_label->font_list().DeriveWithSizeDelta(kFontSizeDelta));
59  }
60  // TODO(satorux): Maybe we need to use language specific fonts for
61  // candidate_label, like Chinese font for Chinese input method?
62  shortcut_label->SetEnabledColor(theme.GetSystemColor(
63      ui::NativeTheme::kColorId_LabelEnabledColor));
64  shortcut_label->SetDisabledColor(theme.GetSystemColor(
65      ui::NativeTheme::kColorId_LabelDisabledColor));
66
67  // Setup paddings.
68  const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6);
69  const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0);
70  const gfx::Insets insets =
71      (orientation == ui::CandidateWindow::VERTICAL ?
72       kVerticalShortcutLabelInsets :
73       kHorizontalShortcutLabelInsets);
74  shortcut_label->SetBorder(views::Border::CreateEmptyBorder(
75      insets.top(), insets.left(), insets.bottom(), insets.right()));
76
77  // Add decoration based on the orientation.
78  if (orientation == ui::CandidateWindow::VERTICAL) {
79    // Set the background color.
80    SkColor blackish = color_utils::AlphaBlend(
81        SK_ColorBLACK,
82        theme.GetSystemColor(ui::NativeTheme::kColorId_WindowBackground),
83        0x40);
84    SkColor transparent_blakish = color_utils::AlphaBlend(
85        SK_ColorTRANSPARENT, blackish, 0xE0);
86    shortcut_label->set_background(
87        views::Background::CreateSolidBackground(transparent_blakish));
88  }
89
90  return shortcut_label;
91}
92
93// Creates the candidate label, and returns it (never returns NULL).
94// The label text is not set in this function.
95views::Label* CreateCandidateLabel(
96    ui::CandidateWindow::Orientation orientation) {
97  views::Label* candidate_label = NULL;
98
99  // Create the candidate label. The label will be added to |this| as a
100  // child view, hence it's deleted when |this| is deleted.
101  if (orientation == ui::CandidateWindow::VERTICAL) {
102    candidate_label = new VerticalCandidateLabel;
103  } else {
104    candidate_label = new views::Label;
105  }
106
107  // Change the font size.
108  candidate_label->SetFontList(
109      candidate_label->font_list().DeriveWithSizeDelta(kFontSizeDelta));
110  candidate_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
111
112  return candidate_label;
113}
114
115// Creates the annotation label, and return it (never returns NULL).
116// The label text is not set in this function.
117views::Label* CreateAnnotationLabel(
118    ui::CandidateWindow::Orientation orientation,
119    const ui::NativeTheme& theme) {
120  // Create the annotation label.
121  views::Label* annotation_label = new views::Label;
122
123  // Change the font size and color.
124  annotation_label->SetFontList(
125      annotation_label->font_list().DeriveWithSizeDelta(kFontSizeDelta));
126  annotation_label->SetEnabledColor(theme.GetSystemColor(
127      ui::NativeTheme::kColorId_LabelDisabledColor));
128  annotation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
129
130  return annotation_label;
131}
132
133}  // namespace
134
135CandidateView::CandidateView(
136    views::ButtonListener* listener,
137    ui::CandidateWindow::Orientation orientation)
138    : views::CustomButton(listener),
139      orientation_(orientation),
140      shortcut_label_(NULL),
141      candidate_label_(NULL),
142      annotation_label_(NULL),
143      infolist_icon_(NULL),
144      shortcut_width_(0),
145      candidate_width_(0),
146      highlighted_(false) {
147  SetBorder(views::Border::CreateEmptyBorder(1, 1, 1, 1));
148
149  const ui::NativeTheme& theme = *GetNativeTheme();
150  shortcut_label_ = CreateShortcutLabel(orientation, theme);
151  candidate_label_ = CreateCandidateLabel(orientation);
152  annotation_label_ = CreateAnnotationLabel(orientation, theme);
153
154  AddChildView(shortcut_label_);
155  AddChildView(candidate_label_);
156  AddChildView(annotation_label_);
157
158  if (orientation == ui::CandidateWindow::VERTICAL) {
159    infolist_icon_ = new views::View;
160    infolist_icon_->set_background(
161        views::Background::CreateSolidBackground(theme.GetSystemColor(
162            ui::NativeTheme::kColorId_FocusedBorderColor)));
163    AddChildView(infolist_icon_);
164  }
165}
166
167void CandidateView::GetPreferredWidths(int* shortcut_width,
168                                       int* candidate_width) {
169  *shortcut_width = shortcut_label_->GetPreferredSize().width();
170  *candidate_width = candidate_label_->GetPreferredSize().width();
171}
172
173void CandidateView::SetWidths(int shortcut_width, int candidate_width) {
174  shortcut_width_ = shortcut_width;
175  shortcut_label_->SetVisible(shortcut_width_ != 0);
176  candidate_width_ = candidate_width;
177}
178
179void CandidateView::SetEntry(const ui::CandidateWindow::Entry& entry) {
180  base::string16 label = entry.label;
181  if (!label.empty() && orientation_ != ui::CandidateWindow::VERTICAL)
182    label += base::ASCIIToUTF16(".");
183  shortcut_label_->SetText(label);
184  candidate_label_->SetText(entry.value);
185  annotation_label_->SetText(entry.annotation);
186}
187
188void CandidateView::SetInfolistIcon(bool enable) {
189  if (infolist_icon_)
190    infolist_icon_->SetVisible(enable);
191  SchedulePaint();
192}
193
194void CandidateView::SetHighlighted(bool highlighted) {
195  if (highlighted_ == highlighted)
196    return;
197
198  highlighted_ = highlighted;
199  if (highlighted) {
200    ui::NativeTheme* theme = GetNativeTheme();
201    set_background(
202        views::Background::CreateSolidBackground(theme->GetSystemColor(
203            ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
204    SetBorder(views::Border::CreateSolidBorder(
205        1,
206        theme->GetSystemColor(ui::NativeTheme::kColorId_FocusedBorderColor)));
207
208    // Cancel currently focused one.
209    for (int i = 0; i < parent()->child_count(); ++i) {
210      CandidateView* view =
211          static_cast<CandidateView*>((parent()->child_at(i)));
212      if (view != this)
213        view->SetHighlighted(false);
214    }
215  } else {
216    set_background(NULL);
217    SetBorder(views::Border::CreateEmptyBorder(1, 1, 1, 1));
218  }
219  SchedulePaint();
220}
221
222void CandidateView::StateChanged() {
223  shortcut_label_->SetEnabled(state() != STATE_DISABLED);
224  if (state() == STATE_PRESSED)
225    SetHighlighted(true);
226}
227
228bool CandidateView::OnMouseDragged(const ui::MouseEvent& event) {
229  if (!HitTestPoint(event.location())) {
230    // Moves the drag target to the sibling view.
231    gfx::Point location_in_widget(event.location());
232    ConvertPointToWidget(this, &location_in_widget);
233    for (int i = 0; i < parent()->child_count(); ++i) {
234      CandidateView* sibling =
235          static_cast<CandidateView*>(parent()->child_at(i));
236      if (sibling == this)
237        continue;
238      gfx::Point location_in_sibling(location_in_widget);
239      ConvertPointFromWidget(sibling, &location_in_sibling);
240      if (sibling->HitTestPoint(location_in_sibling)) {
241        GetWidget()->GetRootView()->SetMouseHandler(sibling);
242        sibling->SetHighlighted(true);
243        return sibling->OnMouseDragged(ui::MouseEvent(event, this, sibling));
244      }
245    }
246
247    return false;
248  }
249
250  return views::CustomButton::OnMouseDragged(event);
251}
252
253void CandidateView::Layout() {
254  const int padding_width =
255      orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6;
256  int x = 0;
257  shortcut_label_->SetBounds(x, 0, shortcut_width_, height());
258  if (shortcut_width_ > 0)
259    x += shortcut_width_ + padding_width;
260  candidate_label_->SetBounds(x, 0, candidate_width_, height());
261  x += candidate_width_ + padding_width;
262
263  int right = bounds().right();
264  if (infolist_icon_ && infolist_icon_->visible()) {
265    infolist_icon_->SetBounds(
266        right - kInfolistIndicatorIconWidth - kInfolistIndicatorIconPadding,
267        kInfolistIndicatorIconPadding,
268        kInfolistIndicatorIconWidth,
269        height() - kInfolistIndicatorIconPadding * 2);
270    right -= kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2;
271  }
272  annotation_label_->SetBounds(x, 0, right - x, height());
273}
274
275gfx::Size CandidateView::GetPreferredSize() const {
276  const int padding_width =
277      orientation_ == ui::CandidateWindow::VERTICAL ? 4 : 6;
278  gfx::Size size;
279  if (shortcut_label_->visible()) {
280    size = shortcut_label_->GetPreferredSize();
281    size.SetToMax(gfx::Size(shortcut_width_, 0));
282    size.Enlarge(padding_width, 0);
283  }
284  gfx::Size candidate_size = candidate_label_->GetPreferredSize();
285  candidate_size.SetToMax(gfx::Size(candidate_width_, 0));
286  size.Enlarge(candidate_size.width() + padding_width, 0);
287  size.SetToMax(candidate_size);
288  if (annotation_label_->visible()) {
289    gfx::Size annotation_size = annotation_label_->GetPreferredSize();
290    size.Enlarge(annotation_size.width() + padding_width, 0);
291    size.SetToMax(annotation_size);
292  }
293
294  // Reserves the margin for infolist_icon even if it's not visible.
295  size.Enlarge(
296      kInfolistIndicatorIconWidth + kInfolistIndicatorIconPadding * 2, 0);
297  return size;
298}
299
300}  // namespace ime
301}  // namespace ash
302