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