Home | History | Annotate | Download | only in input_method
      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