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/infolist_window.h"
6
7#include <string>
8#include <vector>
9
10#include "ash/ime/candidate_window_constants.h"
11#include "base/logging.h"
12#include "grit/ash_strings.h"
13#include "ui/base/l10n/l10n_util.h"
14#include "ui/gfx/color_utils.h"
15#include "ui/native_theme/native_theme.h"
16#include "ui/views/background.h"
17#include "ui/views/border.h"
18#include "ui/views/bubble/bubble_border.h"
19#include "ui/views/bubble/bubble_frame_view.h"
20#include "ui/views/controls/label.h"
21#include "ui/views/layout/box_layout.h"
22#include "ui/views/widget/widget.h"
23#include "ui/wm/core/window_animations.h"
24
25namespace ash {
26namespace ime {
27
28namespace {
29// The width of an info-list.
30const int kInfolistEntryWidth = 200;
31
32// The milliseconds of the delay to show the infolist window.
33const int kInfolistShowDelayMilliSeconds = 500;
34// The milliseconds of the delay to hide the infolist window.
35const int kInfolistHideDelayMilliSeconds = 500;
36
37///////////////////////////////////////////////////////////////////////////////
38// InfolistBorder
39// The BubbleBorder subclass to draw the border and determine its position.
40class InfolistBorder : public views::BubbleBorder {
41 public:
42  InfolistBorder();
43  virtual ~InfolistBorder();
44
45  // views::BubbleBorder implementation.
46  virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect,
47                              const gfx::Size& contents_size) const OVERRIDE;
48  virtual gfx::Insets GetInsets() const OVERRIDE;
49
50 private:
51  DISALLOW_COPY_AND_ASSIGN(InfolistBorder);
52};
53
54InfolistBorder::InfolistBorder()
55    : views::BubbleBorder(views::BubbleBorder::LEFT_CENTER,
56                          views::BubbleBorder::NO_SHADOW,
57                          SK_ColorTRANSPARENT) {
58  set_paint_arrow(views::BubbleBorder::PAINT_NONE);
59}
60
61InfolistBorder::~InfolistBorder() {}
62
63gfx::Rect InfolistBorder::GetBounds(const gfx::Rect& anchor_rect,
64                                    const gfx::Size& contents_size) const {
65  gfx::Rect bounds(contents_size);
66  bounds.set_x(is_arrow_on_left(arrow()) ?
67               anchor_rect.right() : anchor_rect.x() - contents_size.width());
68  // InfolistBorder modifies the vertical position based on the arrow offset
69  // although it doesn't draw the arrow. The arrow offset is the half of
70  // |contents_size| by default but can be modified through the off-screen logic
71  // in BubbleFrameView.
72  bounds.set_y(anchor_rect.y() + contents_size.height() / 2 -
73               GetArrowOffset(contents_size));
74  return bounds;
75}
76
77gfx::Insets InfolistBorder::GetInsets() const {
78  // This has to be specified and return empty insets to place the infolist
79  // window without the gap.
80  return gfx::Insets();
81}
82
83}  // namespace
84
85// InfolistRow renderes a row of a infolist.
86class InfolistEntryView : public views::View {
87 public:
88  InfolistEntryView(const ui::InfolistEntry& entry,
89                    const gfx::FontList& title_font_list,
90                    const gfx::FontList& description_font_list);
91  virtual ~InfolistEntryView();
92
93  void SetEntry(const ui::InfolistEntry& entry);
94
95 private:
96  // views::View implementation.
97  virtual gfx::Size GetPreferredSize() const OVERRIDE;
98
99  void UpdateBackground();
100
101  ui::InfolistEntry entry_;
102
103  // The title label. Owned by views hierarchy.
104  views::Label* title_label_;
105
106  // The description label. Owned by views hierarchy.
107  views::Label* description_label_;
108
109  DISALLOW_COPY_AND_ASSIGN(InfolistEntryView);
110};
111
112InfolistEntryView::InfolistEntryView(const ui::InfolistEntry& entry,
113                                     const gfx::FontList& title_font_list,
114                                     const gfx::FontList& description_font_list)
115    : entry_(entry) {
116  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
117
118  title_label_ = new views::Label(entry.title, title_font_list);
119  title_label_->SetPosition(gfx::Point(0, 0));
120  title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
121  title_label_->SetBorder(views::Border::CreateEmptyBorder(4, 7, 2, 4));
122
123  description_label_ = new views::Label(entry.body, description_font_list);
124  description_label_->SetPosition(gfx::Point(0, 0));
125  description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
126  description_label_->SetMultiLine(true);
127  description_label_->SizeToFit(kInfolistEntryWidth);
128  description_label_->SetBorder(views::Border::CreateEmptyBorder(2, 17, 4, 4));
129  AddChildView(title_label_);
130  AddChildView(description_label_);
131  UpdateBackground();
132}
133
134InfolistEntryView::~InfolistEntryView() {}
135
136void InfolistEntryView::SetEntry(const ui::InfolistEntry& entry) {
137  if (entry_ == entry)
138    return;
139
140  entry_ = entry;
141  title_label_->SetText(entry_.title);
142  description_label_->SetText(entry_.body);
143  UpdateBackground();
144}
145
146gfx::Size InfolistEntryView::GetPreferredSize() const {
147  return gfx::Size(kInfolistEntryWidth, GetHeightForWidth(kInfolistEntryWidth));
148}
149
150void InfolistEntryView::UpdateBackground() {
151  if (entry_.highlighted) {
152    set_background(
153      views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
154          ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
155    SetBorder(views::Border::CreateSolidBorder(
156        1,
157        GetNativeTheme()->GetSystemColor(
158            ui::NativeTheme::kColorId_FocusedBorderColor)));
159  } else {
160    set_background(NULL);
161    SetBorder(views::Border::CreateEmptyBorder(1, 1, 1, 1));
162  }
163  SchedulePaint();
164}
165
166///////////////////////////////////////////////////////////////////////////////
167// InfolistWindow
168
169InfolistWindow::InfolistWindow(views::View* candidate_window,
170                               const std::vector<ui::InfolistEntry>& entries)
171    : views::BubbleDelegateView(candidate_window, views::BubbleBorder::NONE),
172      title_font_list_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 15)),
173      description_font_list_(gfx::Font(kJapaneseFontName,
174                                       kFontSizeDelta + 11)) {
175  set_can_activate(false);
176  set_accept_events(false);
177  set_margins(gfx::Insets());
178
179  set_background(
180      views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
181          ui::NativeTheme::kColorId_WindowBackground)));
182  SetBorder(views::Border::CreateSolidBorder(
183      1,
184      GetNativeTheme()->GetSystemColor(
185          ui::NativeTheme::kColorId_MenuBorderColor)));
186
187  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
188
189  views::Label* caption_label = new views::Label(
190      l10n_util::GetStringUTF16(IDS_ASH_IME_INFOLIST_WINDOW_TITLE));
191  caption_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
192  caption_label->SetEnabledColor(GetNativeTheme()->GetSystemColor(
193      ui::NativeTheme::kColorId_LabelEnabledColor));
194  caption_label->SetBorder(views::Border::CreateEmptyBorder(2, 2, 2, 2));
195  caption_label->set_background(views::Background::CreateSolidBackground(
196      color_utils::AlphaBlend(SK_ColorBLACK,
197                              GetNativeTheme()->GetSystemColor(
198                                  ui::NativeTheme::kColorId_WindowBackground),
199                              0x10)));
200
201  AddChildView(caption_label);
202
203  for (size_t i = 0; i < entries.size(); ++i) {
204    entry_views_.push_back(new InfolistEntryView(
205        entries[i], title_font_list_, description_font_list_));
206    AddChildView(entry_views_.back());
207  }
208}
209
210InfolistWindow::~InfolistWindow() {
211}
212
213void InfolistWindow::InitWidget() {
214  views::Widget* widget = views::BubbleDelegateView::CreateBubble(this);
215  wm::SetWindowVisibilityAnimationType(
216      widget->GetNativeView(),
217      wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
218
219  // BubbleFrameView will be initialized through CreateBubble.
220  GetBubbleFrameView()->SetBubbleBorder(
221      scoped_ptr<views::BubbleBorder>(new InfolistBorder()));
222  SizeToContents();
223}
224
225void InfolistWindow::Relayout(const std::vector<ui::InfolistEntry>& entries) {
226  size_t i = 0;
227  for (; i < entries.size(); ++i) {
228    if (i < entry_views_.size()) {
229      entry_views_[i]->SetEntry(entries[i]);
230    } else {
231      InfolistEntryView* new_entry = new InfolistEntryView(
232          entries[i], title_font_list_, description_font_list_);
233      AddChildView(new_entry);
234      entry_views_.push_back(new_entry);
235    }
236  }
237
238  if (i < entry_views_.size()) {
239    for (; i < entry_views_.size(); ++i)
240      delete entry_views_[i];
241    entry_views_.resize(entries.size());
242  }
243
244  Layout();
245  GetBubbleFrameView()->bubble_border()->set_arrow_offset(0);
246  SizeToContents();
247}
248
249void InfolistWindow::ShowWithDelay() {
250  show_hide_timer_.Start(
251      FROM_HERE,
252      base::TimeDelta::FromMilliseconds(kInfolistShowDelayMilliSeconds),
253      GetWidget(),
254      &views::Widget::Show);
255}
256
257void InfolistWindow::HideWithDelay() {
258  show_hide_timer_.Start(
259      FROM_HERE,
260      base::TimeDelta::FromMilliseconds(kInfolistHideDelayMilliSeconds),
261      GetWidget(),
262      &views::Widget::Close);
263}
264
265void InfolistWindow::ShowImmediately() {
266  show_hide_timer_.Stop();
267  GetWidget()->Show();
268}
269
270void InfolistWindow::HideImmediately() {
271  show_hide_timer_.Stop();
272  GetWidget()->Close();
273}
274
275void InfolistWindow::WindowClosing() {
276  show_hide_timer_.Stop();
277}
278
279}  // namespace ime
280}  // namespace ash
281