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.
5 #include "chrome/browser/chromeos/input_method/candidate_window_controller_impl.h"
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 "ui/views/widget/widget.h"
23 namespace input_method
{
26 // The milliseconds of the delay to show the infolist window.
27 const int kInfolistShowDelayMilliSeconds
= 500;
28 // The milliseconds of the delay to hide the infolist window.
29 const int kInfolistHideDelayMilliSeconds
= 500;
31 // Converts from ibus::Rect to gfx::Rect.
32 gfx::Rect
IBusRectToGfxRect(const ibus::Rect
& rect
) {
33 return gfx::Rect(rect
.x
, rect
.y
, rect
.width
, rect
.height
);
37 bool CandidateWindowControllerImpl::Init() {
38 // Create the candidate window view.
43 void CandidateWindowControllerImpl::Shutdown() {
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::GetActiveRootWindow(),
57 ash::internal::kShellWindowId_InputMethodContainer
);
60 views::corewm::SetWindowVisibilityAnimationType(
61 frame_
->GetNativeView(),
62 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
64 // Create the candidate window.
65 candidate_window_
= new CandidateWindowView(frame_
.get());
66 candidate_window_
->Init();
67 candidate_window_
->AddObserver(this);
69 frame_
->SetContentsView(candidate_window_
);
72 // Create the infolist window.
73 infolist_window_
.reset(new DelayableWidget
);
74 infolist_window_
->Init(params
);
76 views::corewm::SetWindowVisibilityAnimationType(
77 infolist_window_
->GetNativeView(),
78 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
80 InfolistWindowView
* infolist_view
= new InfolistWindowView
;
81 infolist_view
->Init();
82 infolist_window_
->SetContentsView(infolist_view
);
85 CandidateWindowControllerImpl::CandidateWindowControllerImpl()
86 : candidate_window_(NULL
),
87 latest_infolist_focused_index_(InfolistWindowView::InvalidFocusIndex()) {
88 IBusBridge::Get()->SetCandidateWindowHandler(this);
91 CandidateWindowControllerImpl::~CandidateWindowControllerImpl() {
92 IBusBridge::Get()->SetCandidateWindowHandler(NULL
);
93 candidate_window_
->RemoveObserver(this);
96 void CandidateWindowControllerImpl::HideAuxiliaryText() {
97 candidate_window_
->HideAuxiliaryText();
100 void CandidateWindowControllerImpl::HideLookupTable() {
101 candidate_window_
->HideLookupTable();
102 infolist_window_
->Hide();
105 void CandidateWindowControllerImpl::HidePreeditText() {
106 candidate_window_
->HidePreeditText();
109 void CandidateWindowControllerImpl::SetCursorLocation(
110 const ibus::Rect
& cursor_location
,
111 const ibus::Rect
& composition_head
) {
112 // A workaround for http://crosbug.com/6460. We should ignore very short Y
113 // move to prevent the window from shaking up and down.
114 const int kKeepPositionThreshold
= 2; // px
115 const gfx::Rect
& last_location
=
116 candidate_window_
->cursor_location();
117 const int delta_y
= abs(last_location
.y() - cursor_location
.y
);
118 if ((last_location
.x() == cursor_location
.x
) &&
119 (delta_y
<= kKeepPositionThreshold
)) {
120 DVLOG(1) << "Ignored set_cursor_location signal to prevent window shake";
124 // Remember the cursor location.
125 candidate_window_
->set_cursor_location(IBusRectToGfxRect(cursor_location
));
126 candidate_window_
->set_composition_head_location(
127 IBusRectToGfxRect(composition_head
));
128 // Move the window per the cursor location.
129 candidate_window_
->ResizeAndMoveParentFrame();
130 UpdateInfolistBounds();
133 void CandidateWindowControllerImpl::UpdateAuxiliaryText(
134 const std::string
& utf8_text
,
136 // If it's not visible, hide the auxiliary text and return.
138 candidate_window_
->HideAuxiliaryText();
141 candidate_window_
->UpdateAuxiliaryText(utf8_text
);
142 candidate_window_
->ShowAuxiliaryText();
146 void CandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry(
147 const IBusLookupTable
& lookup_table
,
148 std::vector
<InfolistWindowView::Entry
>* infolist_entries
,
149 size_t* focused_index
) {
150 DCHECK(focused_index
);
151 DCHECK(infolist_entries
);
152 *focused_index
= InfolistWindowView::InvalidFocusIndex();
153 infolist_entries
->clear();
155 const size_t cursor_index_in_page
=
156 lookup_table
.cursor_position() % lookup_table
.page_size();
158 for (size_t i
= 0; i
< lookup_table
.candidates().size(); ++i
) {
159 const IBusLookupTable::Entry
& ibus_entry
=
160 lookup_table
.candidates()[i
];
161 if (ibus_entry
.description_title
.empty() &&
162 ibus_entry
.description_body
.empty())
164 InfolistWindowView::Entry entry
;
165 entry
.title
= ibus_entry
.description_title
;
166 entry
.body
= ibus_entry
.description_body
;
167 infolist_entries
->push_back(entry
);
168 if (i
== cursor_index_in_page
)
169 *focused_index
= infolist_entries
->size() - 1;
174 bool CandidateWindowControllerImpl::ShouldUpdateInfolist(
175 const std::vector
<InfolistWindowView::Entry
>& old_entries
,
176 size_t old_focused_index
,
177 const std::vector
<InfolistWindowView::Entry
>& new_entries
,
178 size_t new_focused_index
) {
179 if (old_entries
.empty() && new_entries
.empty())
181 if (old_entries
.size() != new_entries
.size())
183 if (old_focused_index
!= new_focused_index
)
186 for (size_t i
= 0; i
< old_entries
.size(); ++i
) {
187 if (old_entries
[i
].title
!= new_entries
[i
].title
||
188 old_entries
[i
].body
!= new_entries
[i
].body
) {
195 void CandidateWindowControllerImpl::UpdateLookupTable(
196 const IBusLookupTable
& lookup_table
,
198 // If it's not visible, hide the lookup table and return.
200 candidate_window_
->HideLookupTable();
201 infolist_window_
->Hide();
202 // TODO(nona): Introduce unittests for crbug.com/170036.
203 latest_infolist_entries_
.clear();
207 candidate_window_
->UpdateCandidates(lookup_table
);
208 candidate_window_
->ShowLookupTable();
210 size_t focused_index
= 0;
211 std::vector
<InfolistWindowView::Entry
> infolist_entries
;
212 ConvertLookupTableToInfolistEntry(lookup_table
, &infolist_entries
,
215 // If there is no infolist entry, just hide.
216 if (infolist_entries
.empty()) {
217 infolist_window_
->Hide();
221 // If there is no change, just return.
222 if (!ShouldUpdateInfolist(latest_infolist_entries_
,
223 latest_infolist_focused_index_
,
229 latest_infolist_entries_
= infolist_entries
;
230 latest_infolist_focused_index_
= focused_index
;
232 InfolistWindowView
* view
= static_cast<InfolistWindowView
*>(
233 infolist_window_
->GetContentsView());
235 DLOG(ERROR
) << "Contents View is not InfolistWindowView.";
239 view
->Relayout(infolist_entries
, focused_index
);
240 UpdateInfolistBounds();
242 if (focused_index
< infolist_entries
.size())
243 infolist_window_
->DelayShow(kInfolistShowDelayMilliSeconds
);
245 infolist_window_
->DelayHide(kInfolistHideDelayMilliSeconds
);
248 void CandidateWindowControllerImpl::UpdateInfolistBounds() {
249 InfolistWindowView
* view
= static_cast<InfolistWindowView
*>(
250 infolist_window_
->GetContentsView());
253 const gfx::Rect current_bounds
=
254 infolist_window_
->GetClientAreaBoundsInScreen();
256 gfx::Rect new_bounds
;
257 new_bounds
.set_size(view
->GetPreferredSize());
258 new_bounds
.set_origin(GetInfolistWindowPosition(
259 frame_
->GetClientAreaBoundsInScreen(),
260 ash::Shell::GetScreen()->GetDisplayNearestWindow(
261 infolist_window_
->GetNativeView()).work_area(),
264 if (current_bounds
!= new_bounds
)
265 infolist_window_
->SetBounds(new_bounds
);
268 void CandidateWindowControllerImpl::UpdatePreeditText(
269 const std::string
& utf8_text
, unsigned int cursor
, bool visible
) {
270 // If it's not visible, hide the preedit text and return.
271 if (!visible
|| utf8_text
.empty()) {
272 candidate_window_
->HidePreeditText();
275 candidate_window_
->UpdatePreeditText(utf8_text
);
276 candidate_window_
->ShowPreeditText();
279 void CandidateWindowControllerImpl::OnCandidateCommitted(int index
,
282 IBusEngineHandlerInterface
* engine
= IBusBridge::Get()->GetEngineHandler();
284 engine
->CandidateClicked(index
,
285 static_cast<ibus::IBusMouseButton
>(button
),
289 void CandidateWindowControllerImpl::OnCandidateWindowOpened() {
290 FOR_EACH_OBSERVER(CandidateWindowController::Observer
, observers_
,
291 CandidateWindowOpened());
294 void CandidateWindowControllerImpl::OnCandidateWindowClosed() {
295 FOR_EACH_OBSERVER(CandidateWindowController::Observer
, observers_
,
296 CandidateWindowClosed());
299 void CandidateWindowControllerImpl::AddObserver(
300 CandidateWindowController::Observer
* observer
) {
301 observers_
.AddObserver(observer
);
304 void CandidateWindowControllerImpl::RemoveObserver(
305 CandidateWindowController::Observer
* observer
) {
306 observers_
.RemoveObserver(observer
);
310 gfx::Point
CandidateWindowControllerImpl::GetInfolistWindowPosition(
311 const gfx::Rect
& candidate_window_rect
,
312 const gfx::Rect
& screen_rect
,
313 const gfx::Size
& infolist_window_size
) {
314 gfx::Point
result(candidate_window_rect
.right(), candidate_window_rect
.y());
316 if (candidate_window_rect
.right() + infolist_window_size
.width() >
318 result
.set_x(candidate_window_rect
.x() - infolist_window_size
.width());
320 if (candidate_window_rect
.y() + infolist_window_size
.height() >
321 screen_rect
.bottom())
322 result
.set_y(screen_rect
.bottom() - infolist_window_size
.height());
327 } // namespace input_method
328 } // namespace chromeos