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