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 "mojo/examples/keyboard/keyboard_view.h"
6
7#include <algorithm>
8
9#include "base/strings/string16.h"
10#include "base/strings/utf_string_conversions.h"
11#include "mojo/examples/keyboard/keyboard_delegate.h"
12#include "mojo/examples/keyboard/keys.h"
13#include "ui/events/keycodes/keyboard_code_conversion.h"
14#include "ui/events/keycodes/keyboard_codes.h"
15#include "ui/gfx/canvas.h"
16#include "ui/views/background.h"
17#include "ui/views/controls/button/label_button.h"
18#include "ui/views/controls/button/label_button_border.h"
19
20namespace mojo {
21namespace examples {
22
23namespace {
24
25const int kHorizontalPadding = 6;
26const int kVerticalPadding = 8;
27
28base::string16 GetDisplayString(int key_code, int flags) {
29  return base::string16(1, ui::GetCharacterFromKeyCode(
30                            static_cast<ui::KeyboardCode>(key_code), flags));
31}
32
33// Returns a font that fits in the space provided. |text| is used as a basis
34// for determing the size.
35gfx::FontList CalculateFont(int width, int height, const base::string16& text) {
36  gfx::FontList font;
37  gfx::FontList last_font;
38  while (gfx::Canvas::GetStringWidth(text, font) < width &&
39         font.GetHeight() < height) {
40    last_font = font;
41    font = font.DeriveWithSizeDelta(2);
42  }
43  return last_font;
44}
45
46// Returns the total number of keys in |rows|.
47int NumKeys(const std::vector<const Row*>& rows) {
48  int result = 0;
49  for (size_t i = 0; i < rows.size(); ++i)
50    result += static_cast<int>(rows[i]->num_keys);
51  return result;
52}
53
54}  // namespace
55
56KeyboardView::KeyboardView(KeyboardDelegate* delegate)
57    : delegate_(delegate),
58      max_keys_in_row_(0),
59      keyboard_layout_(KEYBOARD_LAYOUT_ALPHA) {
60  set_background(views::Background::CreateSolidBackground(SK_ColorBLACK));
61  SetRows(GetQWERTYRows());
62}
63
64KeyboardView::~KeyboardView() {
65}
66
67void KeyboardView::Layout() {
68  if (width() == 0 || height() == 0 || rows_.empty() ||
69      last_layout_size_ == bounds().size())
70    return;
71
72  last_layout_size_ = bounds().size();
73
74  const int button_width =
75      (width() - (max_keys_in_row_ - 1) * kHorizontalPadding) /
76      max_keys_in_row_;
77  const int button_height =
78      (height() - (static_cast<int>(rows_.size() - 1) * kVerticalPadding)) /
79      static_cast<int>(rows_.size());
80  const int initial_x = (width() - button_width * max_keys_in_row_ -
81                         kHorizontalPadding * (max_keys_in_row_ - 1)) / 2;
82  for (size_t i = 0; i < rows_.size(); ++i) {
83    LayoutRow(*(rows_[i]), static_cast<int>(i), initial_x, button_width,
84              button_height);
85  }
86
87  views::LabelButtonBorder border(views::Button::STYLE_TEXTBUTTON);
88  gfx::Insets insets(border.GetInsets());
89  gfx::FontList font = CalculateFont(button_width - insets.width(),
90                                     button_height - insets.height(),
91                                     base::ASCIIToUTF16("W"));
92  gfx::FontList special_font = CalculateFont(button_width - insets.width(),
93                                             button_height - insets.height(),
94                                             base::ASCIIToUTF16("?123"));
95  button_font_ = font;
96  ResetFonts(font, special_font);
97}
98
99void KeyboardView::SetLayout(KeyboardLayout keyboard_layout) {
100  if (keyboard_layout_ == keyboard_layout)
101    return;
102
103  keyboard_layout_ = keyboard_layout;
104  last_layout_size_ = gfx::Size();
105  if (keyboard_layout_ == KEYBOARD_LAYOUT_NUMERIC)
106    SetRows(GetNumericRows());
107  else
108    SetRows(GetQWERTYRows());
109  Layout();
110  SchedulePaint();
111}
112
113void KeyboardView::LayoutRow(const Row& row,
114                             int row_index,
115                             int initial_x,
116                             int button_width,
117                             int button_height) {
118  int x = initial_x + row.padding * (button_width + kHorizontalPadding);
119  const int y = row_index * (button_height + kVerticalPadding);
120  for (size_t i = 0; i < row.num_keys; ++i) {
121    views::View* button = GetButton(row_index, static_cast<int>(i));
122    int actual_width = button_width;
123    if (row.keys[i].size > 1) {
124      actual_width = (button_width + kHorizontalPadding) *
125          row.keys[i].size - kHorizontalPadding;
126    }
127    button->SetBounds(x, y, actual_width, button_height);
128    x += actual_width + kHorizontalPadding;
129  }
130}
131
132void KeyboardView::SetRows(const std::vector<const Row*>& rows) {
133  const int num_keys = NumKeys(rows);
134  while (child_count() > num_keys)
135    delete child_at(child_count() - 1);
136  for (int i = child_count(); i < num_keys; ++i)
137    AddChildView(CreateButton());
138
139  last_layout_size_ = gfx::Size();
140
141  rows_ = rows;
142
143  max_keys_in_row_ = 0;
144  for (size_t i = 0; i < rows_.size(); ++i) {
145    max_keys_in_row_ = std::max(max_keys_in_row_,
146                                static_cast<int>(rows_[i]->num_keys));
147    ConfigureButtonsInRow(static_cast<int>(i), *rows_[i]);
148  }
149}
150
151void KeyboardView::ConfigureButtonsInRow(int row_index, const Row& row) {
152  for (size_t i = 0; i < row.num_keys; ++i) {
153    views::LabelButton* button = GetButton(row_index, static_cast<int>(i));
154    const Key& key(row.keys[i]);
155    switch (key.display_code) {
156      case SPECIAL_KEY_SHIFT:
157        // TODO: need image.
158        button->SetText(base::string16());
159        break;
160      case SPECIAL_KEY_NUMERIC:
161        button->SetText(base::ASCIIToUTF16("?123"));
162        break;
163      case SPECIAL_KEY_ALPHA:
164        button->SetText(base::ASCIIToUTF16("ABC"));
165        break;
166      default:
167        button->SetText(GetDisplayString(key.display_code,
168                                         key.event_flags | event_flags()));
169        break;
170    }
171    button->SetState(views::Button::STATE_NORMAL);
172  }
173}
174
175views::View* KeyboardView::CreateButton() {
176  views::LabelButton* button = new views::LabelButton(this, base::string16());
177  button->SetTextColor(views::Button::STATE_NORMAL, SK_ColorWHITE);
178  button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
179  button->set_background(views::Background::CreateSolidBackground(78, 78, 78));
180  button->SetFontList(button_font_);
181  //  button->SetHaloColor(SK_ColorBLACK);
182  // Turn off animations as we reuse buttons in different layouts. If we didn't
183  // do this and you click a button to change the layout then the button you
184  // clicked on would animate the transition even though it may now represent a
185  // different key.
186  button->SetAnimationDuration(0);
187  return button;
188}
189
190views::LabelButton* KeyboardView::GetButton(int row, int column) {
191  int offset = column;
192  for (int i = 0; i < row; ++i)
193    offset += static_cast<int>(rows_[i]->num_keys);
194  return static_cast<views::LabelButton*>(child_at(offset));
195}
196
197const Key& KeyboardView::GetKeyForButton(views::Button* button) const {
198  int index = GetIndexOf(button);
199  DCHECK_NE(-1, index);
200  int row = 0;
201  while (index >= static_cast<int>(rows_[row]->num_keys)) {
202    index -= static_cast<int>(rows_[row]->num_keys);
203    row++;
204  }
205  return rows_[row]->keys[index];
206}
207
208void KeyboardView::ResetFonts(const gfx::FontList& button_font,
209                              const gfx::FontList& special_font) {
210  for (size_t i = 0; i < rows_.size(); ++i) {
211    for (size_t j = 0; j < rows_[i]->num_keys; ++j) {
212      views::LabelButton* button = GetButton(static_cast<int>(i),
213                                             static_cast<int>(j));
214      const Key& key(GetKeyForButton(button));
215      switch (key.display_code) {
216        case SPECIAL_KEY_ALPHA:
217        case SPECIAL_KEY_NUMERIC:
218          button->SetFontList(special_font);
219          break;
220        default:
221          button->SetFontList(button_font);
222          break;
223      }
224    }
225  }
226}
227
228void KeyboardView::ButtonPressed(views::Button* sender,
229                                 const ui::Event& event) {
230  const Key& key(GetKeyForButton(sender));
231  switch (key.display_code) {
232    case SPECIAL_KEY_SHIFT:
233      SetLayout((keyboard_layout_ == KEYBOARD_LAYOUT_SHIFT) ?
234                KEYBOARD_LAYOUT_ALPHA : KEYBOARD_LAYOUT_SHIFT);
235      return;
236    case SPECIAL_KEY_ALPHA:
237      SetLayout(KEYBOARD_LAYOUT_ALPHA);
238      return;
239    case SPECIAL_KEY_NUMERIC:
240      SetLayout(KEYBOARD_LAYOUT_NUMERIC);
241      return;
242    default:
243      break;
244  }
245
246  // Windows isn't happy if we pass in the flags used to get the display string.
247#if defined(OS_WIN)
248  int key_event_flags = 0;
249#else
250  int key_event_flags = key.event_flags;
251#endif
252  delegate_->OnKeyPressed(key.keyboard_code(), key_event_flags | event_flags());
253}
254
255}  // namespace examples
256}  // namespace mojo
257