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