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