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