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