Home | History | Annotate | Download | only in views
      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 "ui/app_list/views/search_result_list_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/time/time.h"
     12 #include "third_party/skia/include/core/SkColor.h"
     13 #include "ui/app_list/app_list_switches.h"
     14 #include "ui/app_list/app_list_view_delegate.h"
     15 #include "ui/app_list/search_result.h"
     16 #include "ui/app_list/views/search_result_list_view_delegate.h"
     17 #include "ui/app_list/views/search_result_view.h"
     18 #include "ui/events/event.h"
     19 #include "ui/gfx/animation/linear_animation.h"
     20 #include "ui/views/background.h"
     21 #include "ui/views/layout/box_layout.h"
     22 
     23 namespace {
     24 
     25 const int kMaxResults = 6;
     26 const int kExperimentAppListMaxResults = 3;
     27 const int kTimeoutIndicatorHeight = 2;
     28 const int kTimeoutFramerate = 60;
     29 const SkColor kTimeoutIndicatorColor = SkColorSetRGB(30, 144, 255);
     30 
     31 }  // namespace
     32 
     33 namespace app_list {
     34 
     35 SearchResultListView::SearchResultListView(
     36     SearchResultListViewDelegate* delegate,
     37     AppListViewDelegate* view_delegate)
     38     : delegate_(delegate),
     39       view_delegate_(view_delegate),
     40       results_(NULL),
     41       results_container_(new views::View),
     42       auto_launch_indicator_(new views::View),
     43       last_visible_index_(0),
     44       selected_index_(-1),
     45       update_factory_(this) {
     46   results_container_->SetLayoutManager(
     47       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
     48 
     49   int max_results = kMaxResults;
     50   if (app_list::switches::IsExperimentalAppListEnabled())
     51     max_results = kExperimentAppListMaxResults;
     52 
     53   for (int i = 0; i < max_results; ++i)
     54     results_container_->AddChildView(new SearchResultView(this));
     55   AddChildView(results_container_);
     56 
     57   auto_launch_indicator_->set_background(
     58       views::Background::CreateSolidBackground(kTimeoutIndicatorColor));
     59   auto_launch_indicator_->SetVisible(false);
     60 
     61   AddChildView(auto_launch_indicator_);
     62 }
     63 
     64 SearchResultListView::~SearchResultListView() {
     65   if (results_)
     66     results_->RemoveObserver(this);
     67 }
     68 
     69 void SearchResultListView::SetResults(AppListModel::SearchResults* results) {
     70   if (results_)
     71     results_->RemoveObserver(this);
     72 
     73   results_ = results;
     74   if (results_)
     75     results_->AddObserver(this);
     76 
     77   Update();
     78 }
     79 
     80 void SearchResultListView::SetSelectedIndex(int selected_index) {
     81   if (selected_index_ == selected_index)
     82     return;
     83 
     84   if (selected_index_ >= 0) {
     85     SearchResultView* selected_view  = GetResultViewAt(selected_index_);
     86     selected_view->ClearSelectedAction();
     87     selected_view->SchedulePaint();
     88   }
     89 
     90   selected_index_ = selected_index;
     91 
     92   if (selected_index_ >= 0) {
     93     SearchResultView* selected_view  = GetResultViewAt(selected_index_);
     94     selected_view->ClearSelectedAction();
     95     selected_view->SchedulePaint();
     96     selected_view->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS,
     97                                             true);
     98   }
     99   if (auto_launch_animation_)
    100     CancelAutoLaunchTimeout();
    101 }
    102 
    103 bool SearchResultListView::IsResultViewSelected(
    104     const SearchResultView* result_view) const {
    105   if (selected_index_ < 0)
    106     return false;
    107 
    108   return static_cast<const SearchResultView*>(
    109       results_container_->child_at(selected_index_)) == result_view;
    110 }
    111 
    112 void SearchResultListView::UpdateAutoLaunchState() {
    113   SetAutoLaunchTimeout(view_delegate_->GetAutoLaunchTimeout());
    114 }
    115 
    116 bool SearchResultListView::OnKeyPressed(const ui::KeyEvent& event) {
    117   if (selected_index_ >= 0 &&
    118       results_container_->child_at(selected_index_)->OnKeyPressed(event)) {
    119     return true;
    120   }
    121 
    122   switch (event.key_code()) {
    123     case ui::VKEY_TAB:
    124       if (event.IsShiftDown())
    125         SetSelectedIndex(std::max(selected_index_ - 1, 0));
    126       else
    127         SetSelectedIndex(std::min(selected_index_ + 1, last_visible_index_));
    128       return true;
    129     case ui::VKEY_UP:
    130       SetSelectedIndex(std::max(selected_index_ - 1, 0));
    131       return true;
    132     case ui::VKEY_DOWN:
    133       SetSelectedIndex(std::min(selected_index_ + 1, last_visible_index_));
    134       return true;
    135     default:
    136       break;
    137   }
    138 
    139   return false;
    140 }
    141 
    142 void SearchResultListView::SetAutoLaunchTimeout(
    143     const base::TimeDelta& timeout) {
    144   if (timeout > base::TimeDelta()) {
    145     auto_launch_indicator_->SetVisible(true);
    146     auto_launch_indicator_->SetBounds(0, 0, 0, kTimeoutIndicatorHeight);
    147     auto_launch_animation_.reset(new gfx::LinearAnimation(
    148         timeout.InMilliseconds(), kTimeoutFramerate, this));
    149     auto_launch_animation_->Start();
    150   } else {
    151     auto_launch_indicator_->SetVisible(false);
    152     auto_launch_animation_.reset();
    153   }
    154 }
    155 
    156 void SearchResultListView::CancelAutoLaunchTimeout() {
    157   SetAutoLaunchTimeout(base::TimeDelta());
    158   view_delegate_->AutoLaunchCanceled();
    159 }
    160 
    161 SearchResultView* SearchResultListView::GetResultViewAt(int index) {
    162   DCHECK(index >= 0 && index < results_container_->child_count());
    163   return static_cast<SearchResultView*>(results_container_->child_at(index));
    164 }
    165 
    166 void SearchResultListView::Update() {
    167   std::vector<SearchResult*> display_results =
    168       AppListModel::FilterSearchResultsByDisplayType(
    169           results_,
    170           SearchResult::DISPLAY_LIST,
    171           results_container_->child_count());
    172   last_visible_index_ = display_results.size() - 1;
    173 
    174   for (size_t i = 0; i < static_cast<size_t>(results_container_->child_count());
    175        ++i) {
    176     SearchResultView* result_view = GetResultViewAt(i);
    177     if (i < display_results.size()) {
    178       result_view->SetResult(display_results[i]);
    179       result_view->SetVisible(true);
    180     } else {
    181       result_view->SetResult(NULL);
    182       result_view->SetVisible(false);
    183     }
    184   }
    185   if (selected_index_ > last_visible_index_)
    186     SetSelectedIndex(last_visible_index_);
    187 
    188   Layout();
    189   update_factory_.InvalidateWeakPtrs();
    190   UpdateAutoLaunchState();
    191 }
    192 
    193 void SearchResultListView::ScheduleUpdate() {
    194   // When search results are added one by one, each addition generates an update
    195   // request. Consolidates those update requests into one Update call.
    196   if (!update_factory_.HasWeakPtrs()) {
    197     base::MessageLoop::current()->PostTask(
    198         FROM_HERE,
    199         base::Bind(&SearchResultListView::Update,
    200                    update_factory_.GetWeakPtr()));
    201   }
    202 }
    203 
    204 void SearchResultListView::ForceAutoLaunchForTest() {
    205   if (auto_launch_animation_)
    206     AnimationEnded(auto_launch_animation_.get());
    207 }
    208 
    209 void SearchResultListView::Layout() {
    210   results_container_->SetBoundsRect(GetLocalBounds());
    211 }
    212 
    213 gfx::Size SearchResultListView::GetPreferredSize() const {
    214   return results_container_->GetPreferredSize();
    215 }
    216 
    217 int SearchResultListView::GetHeightForWidth(int w) const {
    218   return results_container_->GetHeightForWidth(w);
    219 }
    220 
    221 void SearchResultListView::VisibilityChanged(views::View* starting_from,
    222                                              bool is_visible) {
    223   if (is_visible)
    224     UpdateAutoLaunchState();
    225   else
    226     CancelAutoLaunchTimeout();
    227 }
    228 
    229 void SearchResultListView::AnimationEnded(const gfx::Animation* animation) {
    230   DCHECK_EQ(auto_launch_animation_.get(), animation);
    231   view_delegate_->OpenSearchResult(results_->GetItemAt(0), true, ui::EF_NONE);
    232 
    233   // The auto-launch has to be canceled explicitly. Think that one of searcher
    234   // is extremely slow. Sometimes the events would happen in the following
    235   // order:
    236   //  1. The search results arrive, auto-launch is dispatched
    237   //  2. Timed out and auto-launch the first search result
    238   //  3. Then another searcher adds search results more
    239   // At the step 3, we shouldn't dispatch the auto-launch again.
    240   CancelAutoLaunchTimeout();
    241 }
    242 
    243 void SearchResultListView::AnimationProgressed(
    244     const gfx::Animation* animation) {
    245   DCHECK_EQ(auto_launch_animation_.get(), animation);
    246   int indicator_width = auto_launch_animation_->CurrentValueBetween(0, width());
    247   auto_launch_indicator_->SetBounds(
    248       0, 0, indicator_width, kTimeoutIndicatorHeight);
    249 }
    250 
    251 void SearchResultListView::ListItemsAdded(size_t start, size_t count) {
    252   ScheduleUpdate();
    253 }
    254 
    255 void SearchResultListView::ListItemsRemoved(size_t start, size_t count) {
    256   size_t last = std::min(
    257       start + count,
    258       static_cast<size_t>(results_container_->child_count()));
    259   for (size_t i = start; i < last; ++i)
    260     GetResultViewAt(i)->ClearResultNoRepaint();
    261 
    262   ScheduleUpdate();
    263 }
    264 
    265 void SearchResultListView::ListItemMoved(size_t index, size_t target_index) {
    266   NOTREACHED();
    267 }
    268 
    269 void SearchResultListView::ListItemsChanged(size_t start, size_t count) {
    270   NOTREACHED();
    271 }
    272 
    273 void SearchResultListView::SearchResultActivated(SearchResultView* view,
    274                                                  int event_flags) {
    275   if (view_delegate_ && view->result())
    276     view_delegate_->OpenSearchResult(view->result(), false, event_flags);
    277 }
    278 
    279 void SearchResultListView::SearchResultActionActivated(SearchResultView* view,
    280                                                        size_t action_index,
    281                                                        int event_flags) {
    282   if (view_delegate_ && view->result()) {
    283     view_delegate_->InvokeSearchResultAction(
    284         view->result(), action_index, event_flags);
    285   }
    286 }
    287 
    288 void SearchResultListView::OnSearchResultInstalled(SearchResultView* view) {
    289   if (delegate_ && view->result())
    290     delegate_->OnResultInstalled(view->result());
    291 }
    292 
    293 void SearchResultListView::OnSearchResultUninstalled(SearchResultView* view) {
    294   if (delegate_ && view->result())
    295     delegate_->OnResultUninstalled(view->result());
    296 }
    297 
    298 }  // namespace app_list
    299