blob: c844ef2359b9149d69bfea23939a377d7e2e81f1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/app_list/views/search_result_list_view.h"
#include <algorithm>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/app_list/app_list_switches.h"
#include "ui/app_list/app_list_view_delegate.h"
#include "ui/app_list/views/search_result_list_view_delegate.h"
#include "ui/app_list/views/search_result_view.h"
#include "ui/events/event.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
namespace {
const int kMaxResults = 6;
const int kExperimentAppListMaxResults = 3;
const int kTimeoutIndicatorHeight = 2;
const int kTimeoutFramerate = 60;
const SkColor kTimeoutIndicatorColor = SkColorSetRGB(30, 144, 255);
} // namespace
namespace app_list {
SearchResultListView::SearchResultListView(
SearchResultListViewDelegate* delegate,
AppListViewDelegate* view_delegate)
: delegate_(delegate),
view_delegate_(view_delegate),
results_(NULL),
results_container_(new views::View),
auto_launch_indicator_(new views::View),
last_visible_index_(0),
selected_index_(-1),
update_factory_(this) {
results_container_->SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
int max_results = kMaxResults;
if (app_list::switches::IsExperimentalAppListEnabled())
max_results = kExperimentAppListMaxResults;
for (int i = 0; i < max_results; ++i)
results_container_->AddChildView(new SearchResultView(this));
AddChildView(results_container_);
auto_launch_indicator_->set_background(
views::Background::CreateSolidBackground(kTimeoutIndicatorColor));
auto_launch_indicator_->SetVisible(false);
AddChildView(auto_launch_indicator_);
}
SearchResultListView::~SearchResultListView() {
if (results_)
results_->RemoveObserver(this);
}
void SearchResultListView::SetResults(AppListModel::SearchResults* results) {
if (results_)
results_->RemoveObserver(this);
results_ = results;
if (results_)
results_->AddObserver(this);
Update();
}
void SearchResultListView::SetSelectedIndex(int selected_index) {
if (selected_index_ == selected_index)
return;
if (selected_index_ >= 0) {
SearchResultView* selected_view = GetResultViewAt(selected_index_);
selected_view->ClearSelectedAction();
selected_view->SchedulePaint();
}
selected_index_ = selected_index;
if (selected_index_ >= 0) {
SearchResultView* selected_view = GetResultViewAt(selected_index_);
selected_view->ClearSelectedAction();
selected_view->SchedulePaint();
selected_view->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS,
true);
}
if (auto_launch_animation_)
CancelAutoLaunchTimeout();
}
bool SearchResultListView::IsResultViewSelected(
const SearchResultView* result_view) const {
if (selected_index_ < 0)
return false;
return static_cast<const SearchResultView*>(
results_container_->child_at(selected_index_)) == result_view;
}
void SearchResultListView::UpdateAutoLaunchState() {
SetAutoLaunchTimeout(view_delegate_->GetAutoLaunchTimeout());
}
bool SearchResultListView::OnKeyPressed(const ui::KeyEvent& event) {
if (selected_index_ >= 0 &&
results_container_->child_at(selected_index_)->OnKeyPressed(event)) {
return true;
}
switch (event.key_code()) {
case ui::VKEY_TAB:
if (event.IsShiftDown())
SetSelectedIndex(std::max(selected_index_ - 1, 0));
else
SetSelectedIndex(std::min(selected_index_ + 1, last_visible_index_));
return true;
case ui::VKEY_UP:
SetSelectedIndex(std::max(selected_index_ - 1, 0));
return true;
case ui::VKEY_DOWN:
SetSelectedIndex(std::min(selected_index_ + 1, last_visible_index_));
return true;
default:
break;
}
return false;
}
void SearchResultListView::SetAutoLaunchTimeout(
const base::TimeDelta& timeout) {
if (timeout > base::TimeDelta()) {
auto_launch_indicator_->SetVisible(true);
auto_launch_indicator_->SetBounds(0, 0, 0, kTimeoutIndicatorHeight);
auto_launch_animation_.reset(new gfx::LinearAnimation(
timeout.InMilliseconds(), kTimeoutFramerate, this));
auto_launch_animation_->Start();
} else {
auto_launch_indicator_->SetVisible(false);
auto_launch_animation_.reset();
}
}
void SearchResultListView::CancelAutoLaunchTimeout() {
SetAutoLaunchTimeout(base::TimeDelta());
view_delegate_->AutoLaunchCanceled();
}
SearchResultView* SearchResultListView::GetResultViewAt(int index) {
DCHECK(index >= 0 && index < results_container_->child_count());
return static_cast<SearchResultView*>(results_container_->child_at(index));
}
void SearchResultListView::Update() {
last_visible_index_ = 0;
for (size_t i = 0; i < static_cast<size_t>(results_container_->child_count());
++i) {
SearchResultView* result_view = GetResultViewAt(i);
if (i < results_->item_count()) {
result_view->SetResult(results_->GetItemAt(i));
result_view->SetVisible(true);
last_visible_index_ = i;
} else {
result_view->SetResult(NULL);
result_view->SetVisible(false);
}
}
if (selected_index_ > last_visible_index_)
SetSelectedIndex(last_visible_index_);
Layout();
update_factory_.InvalidateWeakPtrs();
UpdateAutoLaunchState();
}
void SearchResultListView::ScheduleUpdate() {
// When search results are added one by one, each addition generates an update
// request. Consolidates those update requests into one Update call.
if (!update_factory_.HasWeakPtrs()) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SearchResultListView::Update,
update_factory_.GetWeakPtr()));
}
}
void SearchResultListView::ForceAutoLaunchForTest() {
if (auto_launch_animation_)
AnimationEnded(auto_launch_animation_.get());
}
void SearchResultListView::Layout() {
results_container_->SetBoundsRect(GetLocalBounds());
}
gfx::Size SearchResultListView::GetPreferredSize() const {
return results_container_->GetPreferredSize();
}
int SearchResultListView::GetHeightForWidth(int w) const {
return results_container_->GetHeightForWidth(w);
}
void SearchResultListView::VisibilityChanged(views::View* starting_from,
bool is_visible) {
if (is_visible)
UpdateAutoLaunchState();
else
CancelAutoLaunchTimeout();
}
void SearchResultListView::AnimationEnded(const gfx::Animation* animation) {
DCHECK_EQ(auto_launch_animation_.get(), animation);
view_delegate_->OpenSearchResult(results_->GetItemAt(0), true, ui::EF_NONE);
// The auto-launch has to be canceled explicitly. Think that one of searcher
// is extremely slow. Sometimes the events would happen in the following
// order:
// 1. The search results arrive, auto-launch is dispatched
// 2. Timed out and auto-launch the first search result
// 3. Then another searcher adds search results more
// At the step 3, we shouldn't dispatch the auto-launch again.
CancelAutoLaunchTimeout();
}
void SearchResultListView::AnimationProgressed(
const gfx::Animation* animation) {
DCHECK_EQ(auto_launch_animation_.get(), animation);
int indicator_width = auto_launch_animation_->CurrentValueBetween(0, width());
auto_launch_indicator_->SetBounds(
0, 0, indicator_width, kTimeoutIndicatorHeight);
}
void SearchResultListView::ListItemsAdded(size_t start, size_t count) {
ScheduleUpdate();
}
void SearchResultListView::ListItemsRemoved(size_t start, size_t count) {
size_t last = std::min(
start + count,
static_cast<size_t>(results_container_->child_count()));
for (size_t i = start; i < last; ++i)
GetResultViewAt(i)->ClearResultNoRepaint();
ScheduleUpdate();
}
void SearchResultListView::ListItemMoved(size_t index, size_t target_index) {
NOTREACHED();
}
void SearchResultListView::ListItemsChanged(size_t start, size_t count) {
ScheduleUpdate();
}
void SearchResultListView::SearchResultActivated(SearchResultView* view,
int event_flags) {
if (view_delegate_ && view->result())
view_delegate_->OpenSearchResult(view->result(), false, event_flags);
}
void SearchResultListView::SearchResultActionActivated(SearchResultView* view,
size_t action_index,
int event_flags) {
if (view_delegate_ && view->result()) {
view_delegate_->InvokeSearchResultAction(
view->result(), action_index, event_flags);
}
}
void SearchResultListView::OnSearchResultInstalled(SearchResultView* view) {
if (delegate_ && view->result())
delegate_->OnResultInstalled(view->result());
}
void SearchResultListView::OnSearchResultUninstalled(SearchResultView* view) {
if (delegate_ && view->result())
delegate_->OnResultUninstalled(view->result());
}
} // namespace app_list