1// Copyright (c) 2012 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/system/ime/tray_ime.h"
6
7#include <vector>
8
9#include "ash/metrics/user_metrics_recorder.h"
10#include "ash/root_window_controller.h"
11#include "ash/session/session_state_delegate.h"
12#include "ash/shelf/shelf_widget.h"
13#include "ash/shell.h"
14#include "ash/system/tray/hover_highlight_view.h"
15#include "ash/system/tray/system_tray.h"
16#include "ash/system/tray/system_tray_delegate.h"
17#include "ash/system/tray/system_tray_notifier.h"
18#include "ash/system/tray/tray_constants.h"
19#include "ash/system/tray/tray_details_view.h"
20#include "ash/system/tray/tray_item_more.h"
21#include "ash/system/tray/tray_item_view.h"
22#include "ash/system/tray/tray_utils.h"
23#include "base/logging.h"
24#include "base/strings/utf_string_conversions.h"
25#include "grit/ash_resources.h"
26#include "grit/ash_strings.h"
27#include "ui/accessibility/ax_enums.h"
28#include "ui/accessibility/ax_view_state.h"
29#include "ui/base/resource/resource_bundle.h"
30#include "ui/gfx/font.h"
31#include "ui/gfx/image/image.h"
32#include "ui/views/controls/label.h"
33#include "ui/views/layout/box_layout.h"
34#include "ui/views/widget/widget.h"
35
36namespace ash {
37namespace tray {
38
39// A |HoverHighlightView| that uses bold or normal font depending on whetehr
40// it is selected.  This view exposes itself as a checkbox to the accessibility
41// framework.
42class SelectableHoverHighlightView : public HoverHighlightView {
43 public:
44  SelectableHoverHighlightView(ViewClickListener* listener,
45                               const base::string16& label,
46                               bool selected)
47      : HoverHighlightView(listener), selected_(selected) {
48    AddLabel(
49        label, gfx::ALIGN_LEFT, selected ? gfx::Font::BOLD : gfx::Font::NORMAL);
50  }
51
52  virtual ~SelectableHoverHighlightView() {}
53
54 protected:
55  // Overridden from views::View.
56  virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
57    HoverHighlightView::GetAccessibleState(state);
58    state->role = ui::AX_ROLE_CHECK_BOX;
59    if (selected_)
60      state->AddStateFlag(ui::AX_STATE_CHECKED);
61  }
62
63 private:
64  bool selected_;
65
66  DISALLOW_COPY_AND_ASSIGN(SelectableHoverHighlightView);
67};
68
69class IMEDefaultView : public TrayItemMore {
70 public:
71  explicit IMEDefaultView(SystemTrayItem* owner)
72      : TrayItemMore(owner, true) {
73    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
74
75    SetImage(bundle.GetImageNamed(
76        IDR_AURA_UBER_TRAY_IME).ToImageSkia());
77
78    IMEInfo info;
79    Shell::GetInstance()->system_tray_delegate()->GetCurrentIME(&info);
80    UpdateLabel(info);
81  }
82
83  virtual ~IMEDefaultView() {}
84
85  void UpdateLabel(const IMEInfo& info) {
86    SetLabel(info.name);
87    SetAccessibleName(info.name);
88  }
89
90 private:
91  DISALLOW_COPY_AND_ASSIGN(IMEDefaultView);
92};
93
94class IMEDetailedView : public TrayDetailsView,
95                        public ViewClickListener {
96 public:
97  IMEDetailedView(SystemTrayItem* owner, user::LoginStatus login)
98      : TrayDetailsView(owner),
99        login_(login) {
100    SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
101    IMEInfoList list;
102    delegate->GetAvailableIMEList(&list);
103    IMEPropertyInfoList property_list;
104    delegate->GetCurrentIMEProperties(&property_list);
105    Update(list, property_list);
106  }
107
108  virtual ~IMEDetailedView() {}
109
110  void Update(const IMEInfoList& list,
111              const IMEPropertyInfoList& property_list) {
112    Reset();
113
114    AppendIMEList(list);
115    if (!property_list.empty())
116      AppendIMEProperties(property_list);
117    bool userAddingRunning = ash::Shell::GetInstance()
118                                 ->session_state_delegate()
119                                 ->IsInSecondaryLoginScreen();
120
121    if (login_ != user::LOGGED_IN_NONE && login_ != user::LOGGED_IN_LOCKED &&
122        !userAddingRunning)
123      AppendSettings();
124    AppendHeaderEntry();
125
126    Layout();
127    SchedulePaint();
128  }
129
130 private:
131  void AppendHeaderEntry() {
132    CreateSpecialRow(IDS_ASH_STATUS_TRAY_IME, this);
133  }
134
135  void AppendIMEList(const IMEInfoList& list) {
136    ime_map_.clear();
137    CreateScrollableList();
138    for (size_t i = 0; i < list.size(); i++) {
139      HoverHighlightView* container = new SelectableHoverHighlightView(
140          this, list[i].name, list[i].selected);
141      scroll_content()->AddChildView(container);
142      ime_map_[container] = list[i].id;
143    }
144  }
145
146  void AppendIMEProperties(const IMEPropertyInfoList& property_list) {
147    property_map_.clear();
148    for (size_t i = 0; i < property_list.size(); i++) {
149      HoverHighlightView* container = new SelectableHoverHighlightView(
150          this, property_list[i].name, property_list[i].selected);
151      if (i == 0)
152        container->SetBorder(views::Border::CreateSolidSidedBorder(
153            1, 0, 0, 0, kBorderLightColor));
154      scroll_content()->AddChildView(container);
155      property_map_[container] = property_list[i].key;
156    }
157  }
158
159  void AppendSettings() {
160    HoverHighlightView* container = new HoverHighlightView(this);
161    container->AddLabel(
162        ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
163            IDS_ASH_STATUS_TRAY_IME_SETTINGS),
164        gfx::ALIGN_LEFT,
165        gfx::Font::NORMAL);
166    AddChildView(container);
167    settings_ = container;
168  }
169
170  // Overridden from ViewClickListener.
171  virtual void OnViewClicked(views::View* sender) OVERRIDE {
172    SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
173    if (sender == footer()->content()) {
174      TransitionToDefaultView();
175    } else if (sender == settings_) {
176      Shell::GetInstance()->metrics()->RecordUserMetricsAction(
177          ash::UMA_STATUS_AREA_IME_SHOW_DETAILED);
178      delegate->ShowIMESettings();
179    } else {
180      std::map<views::View*, std::string>::const_iterator ime_find;
181      ime_find = ime_map_.find(sender);
182      if (ime_find != ime_map_.end()) {
183        Shell::GetInstance()->metrics()->RecordUserMetricsAction(
184            ash::UMA_STATUS_AREA_IME_SWITCH_MODE);
185        std::string ime_id = ime_find->second;
186        delegate->SwitchIME(ime_id);
187        GetWidget()->Close();
188      } else {
189        std::map<views::View*, std::string>::const_iterator prop_find;
190        prop_find = property_map_.find(sender);
191        if (prop_find != property_map_.end()) {
192          const std::string key = prop_find->second;
193          delegate->ActivateIMEProperty(key);
194          GetWidget()->Close();
195        }
196      }
197    }
198  }
199
200  user::LoginStatus login_;
201
202  std::map<views::View*, std::string> ime_map_;
203  std::map<views::View*, std::string> property_map_;
204  views::View* settings_;
205
206  DISALLOW_COPY_AND_ASSIGN(IMEDetailedView);
207};
208
209}  // namespace tray
210
211TrayIME::TrayIME(SystemTray* system_tray)
212    : SystemTrayItem(system_tray),
213      tray_label_(NULL),
214      default_(NULL),
215      detailed_(NULL) {
216  Shell::GetInstance()->system_tray_notifier()->AddIMEObserver(this);
217}
218
219TrayIME::~TrayIME() {
220  Shell::GetInstance()->system_tray_notifier()->RemoveIMEObserver(this);
221}
222
223void TrayIME::UpdateTrayLabel(const IMEInfo& current, size_t count) {
224  if (tray_label_) {
225    bool visible = count > 1;
226    tray_label_->SetVisible(visible);
227    // Do not change label before hiding because this change is noticeable.
228    if (!visible)
229      return;
230    if (current.third_party) {
231      tray_label_->label()->SetText(
232          current.short_name + base::UTF8ToUTF16("*"));
233    } else {
234      tray_label_->label()->SetText(current.short_name);
235    }
236    SetTrayLabelItemBorder(tray_label_, system_tray()->shelf_alignment());
237    tray_label_->Layout();
238  }
239}
240
241views::View* TrayIME::CreateTrayView(user::LoginStatus status) {
242  CHECK(tray_label_ == NULL);
243  tray_label_ = new TrayItemView(this);
244  tray_label_->CreateLabel();
245  SetupLabelForTray(tray_label_->label());
246  // Hide IME tray when it is created, it will be updated when it is notified
247  // for IME refresh event.
248  tray_label_->SetVisible(false);
249  return tray_label_;
250}
251
252views::View* TrayIME::CreateDefaultView(user::LoginStatus status) {
253  SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
254  IMEInfoList list;
255  IMEPropertyInfoList property_list;
256  delegate->GetAvailableIMEList(&list);
257  delegate->GetCurrentIMEProperties(&property_list);
258  if (list.size() <= 1 && property_list.size() <= 1)
259    return NULL;
260  CHECK(default_ == NULL);
261  default_ = new tray::IMEDefaultView(this);
262  return default_;
263}
264
265views::View* TrayIME::CreateDetailedView(user::LoginStatus status) {
266  CHECK(detailed_ == NULL);
267  detailed_ = new tray::IMEDetailedView(this, status);
268  return detailed_;
269}
270
271void TrayIME::DestroyTrayView() {
272  tray_label_ = NULL;
273}
274
275void TrayIME::DestroyDefaultView() {
276  default_ = NULL;
277}
278
279void TrayIME::DestroyDetailedView() {
280  detailed_ = NULL;
281}
282
283void TrayIME::UpdateAfterLoginStatusChange(user::LoginStatus status) {
284}
285
286void TrayIME::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
287  SetTrayLabelItemBorder(tray_label_, alignment);
288  tray_label_->Layout();
289}
290
291void TrayIME::OnIMERefresh() {
292  SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
293  IMEInfoList list;
294  IMEInfo current;
295  IMEPropertyInfoList property_list;
296  delegate->GetCurrentIME(&current);
297  delegate->GetAvailableIMEList(&list);
298  delegate->GetCurrentIMEProperties(&property_list);
299
300  UpdateTrayLabel(current, list.size());
301
302  if (default_)
303    default_->UpdateLabel(current);
304  if (detailed_)
305    detailed_->Update(list, property_list);
306}
307
308}  // namespace ash
309