candidate_window_controller_impl.cc revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
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 "chrome/browser/chromeos/input_method/candidate_window_controller_impl.h"
6
7#include <string>
8#include <vector>
9
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/observer_list.h"
13#include "chrome/browser/chromeos/input_method/candidate_window_view.h"
14#include "chrome/browser/chromeos/input_method/delayable_widget.h"
15#include "chrome/browser/chromeos/input_method/infolist_window_view.h"
16#include "chromeos/dbus/dbus_thread_manager.h"
17#include "ui/views/widget/widget.h"
18
19#if defined(USE_ASH)
20#include "ash/shell.h"
21#include "ash/shell_window_ids.h"
22#include "ash/wm/window_animations.h"
23#endif  // USE_ASH
24
25namespace chromeos {
26namespace input_method {
27
28namespace {
29// The milliseconds of the delay to show the infolist window.
30const int kInfolistShowDelayMilliSeconds = 500;
31// The milliseconds of the delay to hide the infolist window.
32const int kInfolistHideDelayMilliSeconds = 500;
33
34// Converts from ibus::Rect to gfx::Rect.
35gfx::Rect IBusRectToGfxRect(const ibus::Rect& rect) {
36  return gfx::Rect(rect.x, rect.y, rect.width, rect.height);
37}
38
39// Returns pointer of IBusPanelService. This function returns NULL if it is not
40// ready.
41IBusPanelService* GetIBusPanelService() {
42  return DBusThreadManager::Get()->GetIBusPanelService();
43}
44}  // namespace
45
46bool CandidateWindowControllerImpl::Init() {
47  IBusDaemonController::GetInstance()->AddObserver(this);
48  // Create the candidate window view.
49  CreateView();
50  return true;
51}
52
53void CandidateWindowControllerImpl::Shutdown() {
54  IBusDaemonController::GetInstance()->RemoveObserver(this);
55}
56
57void CandidateWindowControllerImpl::CreateView() {
58  // Create a non-decorated frame.
59  frame_.reset(new views::Widget);
60  // The size is initially zero.
61  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
62  // |frame_| and |infolist_window_| are owned by controller impl so
63  // they should use WIDGET_OWNS_NATIVE_WIDGET ownership.
64  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
65  // Show the candidate window always on top
66#if defined(USE_ASH)
67  params.parent = ash::Shell::GetContainer(
68      ash::Shell::GetActiveRootWindow(),
69      ash::internal::kShellWindowId_InputMethodContainer);
70#else
71  params.keep_on_top = true;
72#endif
73  frame_->Init(params);
74#if defined(USE_ASH)
75  views::corewm::SetWindowVisibilityAnimationType(
76      frame_->GetNativeView(),
77      views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
78#endif  // USE_ASH
79
80  // Create the candidate window.
81  candidate_window_ = new CandidateWindowView(frame_.get());
82  candidate_window_->Init();
83  candidate_window_->AddObserver(this);
84
85  frame_->SetContentsView(candidate_window_);
86
87
88  // Create the infolist window.
89  infolist_window_.reset(new DelayableWidget);
90  infolist_window_->Init(params);
91#if defined(USE_ASH)
92  views::corewm::SetWindowVisibilityAnimationType(
93      infolist_window_->GetNativeView(),
94      views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
95#endif  // USE_ASH
96
97  InfolistWindowView* infolist_view = new InfolistWindowView;
98  infolist_view->Init();
99  infolist_window_->SetContentsView(infolist_view);
100}
101
102CandidateWindowControllerImpl::CandidateWindowControllerImpl()
103    : candidate_window_(NULL),
104      infolist_window_(NULL),
105      latest_infolist_focused_index_(InfolistWindowView::InvalidFocusIndex()) {
106}
107
108CandidateWindowControllerImpl::~CandidateWindowControllerImpl() {
109  if (DBusThreadManager::Get()->GetIBusPanelService())
110    DBusThreadManager::Get()->GetIBusPanelService()->
111        SetUpCandidateWindowHandler(NULL);
112  candidate_window_->RemoveObserver(this);
113}
114
115void CandidateWindowControllerImpl::HideAuxiliaryText() {
116  candidate_window_->HideAuxiliaryText();
117}
118
119void CandidateWindowControllerImpl::HideLookupTable() {
120  candidate_window_->HideLookupTable();
121  infolist_window_->Hide();
122}
123
124void CandidateWindowControllerImpl::HidePreeditText() {
125  candidate_window_->HidePreeditText();
126}
127
128void CandidateWindowControllerImpl::SetCursorLocation(
129    const ibus::Rect& cursor_location,
130    const ibus::Rect& composition_head) {
131  // A workaround for http://crosbug.com/6460. We should ignore very short Y
132  // move to prevent the window from shaking up and down.
133  const int kKeepPositionThreshold = 2;  // px
134  const gfx::Rect& last_location =
135      candidate_window_->cursor_location();
136  const int delta_y = abs(last_location.y() - cursor_location.y);
137  if ((last_location.x() == cursor_location.x) &&
138      (delta_y <= kKeepPositionThreshold)) {
139    DVLOG(1) << "Ignored set_cursor_location signal to prevent window shake";
140    return;
141  }
142
143  // Remember the cursor location.
144  candidate_window_->set_cursor_location(IBusRectToGfxRect(cursor_location));
145  candidate_window_->set_composition_head_location(
146      IBusRectToGfxRect(composition_head));
147  // Move the window per the cursor location.
148  candidate_window_->ResizeAndMoveParentFrame();
149  UpdateInfolistBounds();
150}
151
152void CandidateWindowControllerImpl::UpdateAuxiliaryText(
153    const std::string& utf8_text,
154    bool visible) {
155  // If it's not visible, hide the auxiliary text and return.
156  if (!visible) {
157    candidate_window_->HideAuxiliaryText();
158    return;
159  }
160  candidate_window_->UpdateAuxiliaryText(utf8_text);
161  candidate_window_->ShowAuxiliaryText();
162}
163
164// static
165void CandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry(
166    const IBusLookupTable& lookup_table,
167    std::vector<InfolistWindowView::Entry>* infolist_entries,
168    size_t* focused_index) {
169  DCHECK(focused_index);
170  DCHECK(infolist_entries);
171  *focused_index = InfolistWindowView::InvalidFocusIndex();
172  infolist_entries->clear();
173
174  const size_t cursor_index_in_page =
175      lookup_table.cursor_position() % lookup_table.page_size();
176
177  for (size_t i = 0; i < lookup_table.candidates().size(); ++i) {
178    const IBusLookupTable::Entry& ibus_entry =
179        lookup_table.candidates()[i];
180    if (ibus_entry.description_title.empty() &&
181        ibus_entry.description_body.empty())
182      continue;
183    InfolistWindowView::Entry entry;
184    entry.title = ibus_entry.description_title;
185    entry.body = ibus_entry.description_body;
186    infolist_entries->push_back(entry);
187    if (i == cursor_index_in_page)
188      *focused_index = infolist_entries->size() - 1;
189  }
190}
191
192// static
193bool CandidateWindowControllerImpl::ShouldUpdateInfolist(
194    const std::vector<InfolistWindowView::Entry>& old_entries,
195    size_t old_focused_index,
196    const std::vector<InfolistWindowView::Entry>& new_entries,
197    size_t new_focused_index) {
198  if (old_entries.empty() && new_entries.empty())
199    return false;
200  if (old_entries.size() != new_entries.size())
201    return true;
202  if (old_focused_index != new_focused_index)
203    return true;
204
205  for (size_t i = 0; i < old_entries.size(); ++i) {
206    if (old_entries[i].title != new_entries[i].title ||
207        old_entries[i].body != new_entries[i].body ) {
208      return true;
209    }
210  }
211  return false;
212}
213
214void CandidateWindowControllerImpl::UpdateLookupTable(
215    const IBusLookupTable& lookup_table,
216    bool visible) {
217  // If it's not visible, hide the lookup table and return.
218  if (!visible) {
219    candidate_window_->HideLookupTable();
220    infolist_window_->Hide();
221    // TODO(nona): Introduce unittests for crbug.com/170036.
222    latest_infolist_entries_.clear();
223    return;
224  }
225
226  candidate_window_->UpdateCandidates(lookup_table);
227  candidate_window_->ShowLookupTable();
228
229  size_t focused_index = 0;
230  std::vector<InfolistWindowView::Entry> infolist_entries;
231  ConvertLookupTableToInfolistEntry(lookup_table, &infolist_entries,
232                                    &focused_index);
233
234  // If there is no infolist entry, just hide.
235  if (infolist_entries.empty()) {
236    infolist_window_->Hide();
237    return;
238  }
239
240  // If there is no change, just return.
241  if (!ShouldUpdateInfolist(latest_infolist_entries_,
242                            latest_infolist_focused_index_,
243                            infolist_entries,
244                            focused_index)) {
245    return;
246  }
247
248  latest_infolist_entries_ = infolist_entries;
249  latest_infolist_focused_index_ = focused_index;
250
251  InfolistWindowView* view = static_cast<InfolistWindowView*>(
252      infolist_window_->GetContentsView());
253  if (!view) {
254    DLOG(ERROR) << "Contents View is not InfolistWindowView.";
255    return;
256  }
257
258  view->Relayout(infolist_entries, focused_index);
259  UpdateInfolistBounds();
260
261  if (focused_index < infolist_entries.size())
262    infolist_window_->DelayShow(kInfolistShowDelayMilliSeconds);
263  else
264    infolist_window_->DelayHide(kInfolistHideDelayMilliSeconds);
265}
266
267void CandidateWindowControllerImpl::UpdateInfolistBounds() {
268  InfolistWindowView* view = static_cast<InfolistWindowView*>(
269      infolist_window_->GetContentsView());
270  if (!view)
271    return;
272  const gfx::Rect current_bounds =
273      infolist_window_->GetClientAreaBoundsInScreen();
274
275  gfx::Rect new_bounds;
276  new_bounds.set_size(view->GetPreferredSize());
277  new_bounds.set_origin(GetInfolistWindowPosition(
278        frame_->GetClientAreaBoundsInScreen(),
279        ash::Shell::GetScreen()->GetDisplayNearestWindow(
280            infolist_window_->GetNativeView()).work_area(),
281        new_bounds.size()));
282
283  if (current_bounds != new_bounds)
284    infolist_window_->SetBounds(new_bounds);
285}
286
287void CandidateWindowControllerImpl::UpdatePreeditText(
288    const std::string& utf8_text, unsigned int cursor, bool visible) {
289  // If it's not visible, hide the preedit text and return.
290  if (!visible || utf8_text.empty()) {
291    candidate_window_->HidePreeditText();
292    return;
293  }
294  candidate_window_->UpdatePreeditText(utf8_text);
295  candidate_window_->ShowPreeditText();
296}
297
298void CandidateWindowControllerImpl::OnCandidateCommitted(int index,
299                                                         int button,
300                                                         int flags) {
301  GetIBusPanelService()->CandidateClicked(
302      index,
303      static_cast<ibus::IBusMouseButton>(button),
304      flags);
305}
306
307void CandidateWindowControllerImpl::OnCandidateWindowOpened() {
308  FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_,
309                    CandidateWindowOpened());
310}
311
312void CandidateWindowControllerImpl::OnCandidateWindowClosed() {
313  FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_,
314                    CandidateWindowClosed());
315}
316
317void CandidateWindowControllerImpl::AddObserver(
318    CandidateWindowController::Observer* observer) {
319  observers_.AddObserver(observer);
320}
321
322void CandidateWindowControllerImpl::RemoveObserver(
323    CandidateWindowController::Observer* observer) {
324  observers_.RemoveObserver(observer);
325}
326
327void CandidateWindowControllerImpl::OnConnected() {
328  DBusThreadManager::Get()->GetIBusPanelService()->SetUpCandidateWindowHandler(
329      this);
330}
331
332void CandidateWindowControllerImpl::OnDisconnected() {
333  candidate_window_->HideAll();
334  infolist_window_->Hide();
335  DBusThreadManager::Get()->GetIBusPanelService()->SetUpCandidateWindowHandler(
336      NULL);
337}
338
339// static
340gfx::Point CandidateWindowControllerImpl::GetInfolistWindowPosition(
341    const gfx::Rect& candidate_window_rect,
342    const gfx::Rect& screen_rect,
343    const gfx::Size& infolist_window_size) {
344  gfx::Point result(candidate_window_rect.right(), candidate_window_rect.y());
345
346  if (candidate_window_rect.right() + infolist_window_size.width() >
347      screen_rect.right())
348    result.set_x(candidate_window_rect.x() - infolist_window_size.width());
349
350  if (candidate_window_rect.y() + infolist_window_size.height() >
351      screen_rect.bottom())
352    result.set_y(screen_rect.bottom() - infolist_window_size.height());
353
354  return result;
355}
356
357}  // namespace input_method
358}  // namespace chromeos
359