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