Chromium Code Reviews| Index: ui/app_list/app_list_model.cc |
| diff --git a/ui/app_list/app_list_model.cc b/ui/app_list/app_list_model.cc |
| index 246465685685f97cfd3878f592a87f4418d7f4f0..d039058de1578d93429b4dbdf148df8eabb601ba 100644 |
| --- a/ui/app_list/app_list_model.cc |
| +++ b/ui/app_list/app_list_model.cc |
| @@ -4,10 +4,12 @@ |
| #include "ui/app_list/app_list_model.h" |
| +#include "ui/app_list/app_list_constants.h" |
| #include "ui/app_list/app_list_item_model.h" |
| #include "ui/app_list/app_list_model_observer.h" |
| #include "ui/app_list/search_box_model.h" |
| #include "ui/app_list/search_result.h" |
| +#include "ui/base/models/list_model_observer.h" |
| namespace app_list { |
| @@ -15,15 +17,56 @@ AppListModel::User::User() : active(false) {} |
| AppListModel::User::~User() {} |
| +class AppListModel::ItemPage : public ui::ListModelObserver { |
| + public: |
| + ItemPage(AppListModel* model, size_t page_idx) |
| + : model_(model), |
| + page_idx_(page_idx) { |
| + app_items_.AddObserver(this); |
| + } |
| + virtual ~ItemPage() { |
| + app_items_.RemoveObserver(this); |
| + } |
| + virtual void ListItemsAdded(size_t start, size_t count) OVERRIDE { |
| + FOR_EACH_OBSERVER(AppListModelObserver, |
| + model_->observers_, |
| + OnListItemsAdded(page_idx_, start, count)); |
| + } |
| + virtual void ListItemsRemoved(size_t start, size_t count) OVERRIDE { |
| + FOR_EACH_OBSERVER(AppListModelObserver, |
| + model_->observers_, |
| + OnListItemsRemoved(page_idx_, start, count)); |
| + } |
| + virtual void ListItemMoved(size_t index, size_t target_index) OVERRIDE { |
| + AppListItemModel* item = app_items_.GetItemAt(target_index); |
| + model_->SetItemPosition(item, page_idx_, target_index); |
| + FOR_EACH_OBSERVER(AppListModelObserver, |
| + model_->observers_, |
| + OnListItemMoved(page_idx_, index, target_index)); |
| + } |
| + virtual void ListItemsChanged(size_t start, size_t count) OVERRIDE { |
| + NOTREACHED(); |
| + } |
| + |
| + AppItems* app_items() { return &app_items_; } |
| + |
| + private: |
| + AppListModel* model_; |
| + size_t page_idx_; |
| + AppItems app_items_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ItemPage); |
| +}; |
| + |
| AppListModel::AppListModel() |
| - : apps_(new Apps), |
| - search_box_(new SearchBoxModel), |
| + : search_box_(new SearchBoxModel), |
| results_(new SearchResults), |
| signed_in_(false), |
| status_(STATUS_NORMAL) { |
| } |
| AppListModel::~AppListModel() { |
| + item_pages_.clear(); |
| } |
| void AppListModel::AddObserver(AppListModelObserver* observer) { |
| @@ -62,33 +105,196 @@ void AppListModel::SetSignedIn(bool signed_in) { |
| } |
| AppListItemModel* AppListModel::FindItem(const std::string& id) { |
| - for (size_t i = 0; i < apps_->item_count(); ++i) { |
| - AppListItemModel* item = apps_->GetItemAt(i); |
| - if (item->id() == id) |
| - return item; |
| + for (size_t page = 0; page < item_pages_.size(); ++page) { |
| + AppItems* apps = app_items(page); |
| + for (size_t i = 0; i < apps->item_count(); ++i) { |
| + AppListItemModel* item = apps->GetItemAt(i); |
| + if (item->id() == id) |
| + return item; |
| + } |
| } |
| return NULL; |
| } |
| -void AppListModel::AddItem(AppListItemModel* item) { |
| + |
| +AppListItemModel* AppListModel::GetItemAt(size_t page_idx, size_t item_idx) { |
| + return app_items(page_idx)->GetItemAt(item_idx); |
| +} |
| + |
| +size_t AppListModel::AddItem(AppListItemModel* item) { |
| + CHECK(!FindItem(item->id())); |
| + if (item_pages_.empty()) |
| + return AddItemToPageAtIndex(item, 0, 0); |
| + |
| + // Currently items are kept sorted in the model such that for any item A |
| + // on page 1 and item B on page 2, A.GetSortOrder() < B.GetSortOrder(). This |
| + // also applies to any item A on a page that preceeds B on the same page. |
| std::string sort_order = item->GetSortOrder(); |
| - // Note: ui::ListModel is not a sorted list. |
| - size_t index = 0; |
| - for (; index < apps_->item_count(); ++index) { |
| - if (sort_order < apps_->GetItemAt(index)->GetSortOrder()) |
| + |
| + // First find the page that |item| belongs in. |
| + size_t page_idx = 0; |
| + for (; page_idx < item_pages_.size() - 1; ++page_idx) { |
| + if (sort_order < app_items(page_idx + 1)->GetItemAt(0)->GetSortOrder()) |
| break; |
| } |
| - apps_->AddAt(index, item); |
| + // Items are kept sorted in a page by convention, but are not logically |
| + // gauranteed to be sorted (e.g. if an item's sort order were to change) |
| + // so do a linear search instead of using std::find (also the number of items |
| + // per page is small and a linear search is simpler). |
| + AppItems* apps = app_items(page_idx); |
| + size_t item_idx = 0; |
| + for (; item_idx < apps->item_count(); ++item_idx) { |
| + if (sort_order < apps->GetItemAt(item_idx)->GetSortOrder()) |
| + break; |
| + } |
| + return AddItemToPageAtIndex(item, page_idx, item_idx); |
| +} |
| + |
| +size_t AppListModel::AddItemToPage(AppListItemModel* item, size_t page_idx) { |
| + CHECK(!FindItem(item->id())); |
| + if (page_idx == item_pages_.size()) { |
| + // OK to add an item to [number of app pages] + 1. |
| + return AddItemToPageAtIndex(item, page_idx, 0); |
| + } |
| + std::string sort_order = item->GetSortOrder(); |
| + AppItems* apps = app_items(page_idx); |
| + size_t item_idx = 0; |
| + for (; item_idx < apps->item_count(); ++item_idx) { |
|
xiyuan
2013/10/18 23:04:08
Can we make this loop of insertion an item part of
stevenjb
2013/10/19 01:04:43
That sounds reasonable. I kind of wanted to keep t
|
| + if (sort_order < apps->GetItemAt(item_idx)->GetSortOrder()) |
| + break; |
| + } |
| + return AddItemToPageAtIndex(item, page_idx, item_idx); |
| } |
| void AppListModel::DeleteItem(const std::string& id) { |
| - for (size_t i = 0; i < apps_->item_count(); ++i) { |
| - AppListItemModel* item = apps_->GetItemAt(i); |
| - if (item->id() == id) { |
| - apps_->DeleteAt(i); |
| - return; |
| + for (size_t p = 0; p < item_pages_.size(); ++p) { |
| + AppItems* apps = app_items(p); |
| + for (size_t i = 0; i < apps->item_count(); ++i) { |
| + AppListItemModel* item = apps->GetItemAt(i); |
| + if (item->id() == id) { |
| + apps->DeleteAt(i); |
| + if (apps->item_count() == 0) |
| + RemovePage(p); |
| + return; |
| + } |
| + } |
| + } |
| +} |
| + |
| +void AppListModel::DeleteItemAt(size_t page_idx, size_t item_idx) { |
| + AppItems* apps = app_items(page_idx); |
| + apps->DeleteAt(item_idx); |
| + if (apps->item_count() == 0) |
| + RemovePage(page_idx); |
| +} |
| + |
| +void AppListModel::MoveItem(size_t page_idx, size_t item_idx, |
| + size_t target_page_idx, size_t target_item_idx) { |
| + VLOG(2) << "MoveItem: " << page_idx << ", " << item_idx |
| + << " -> " << target_page_idx << ", " << target_item_idx; |
| + CHECK_LT(page_idx, item_pages_.size()); |
| + CHECK_LE(item_idx, app_items(page_idx)->item_count()); |
| + if (target_page_idx == page_idx) { |
| + CHECK_LT(target_item_idx, app_items(page_idx)->item_count()); |
| + app_items(page_idx)->Move(item_idx, target_item_idx); |
| + // SetItemPosition will get called by ItemPage::ListItemMoved. |
| + } else { |
| + AppListItemModel* item = app_items(page_idx)->RemoveAt(item_idx); |
| + if (target_page_idx >= item_pages_.size()) { |
| + AddPage(); |
| + CHECK_LT(target_page_idx, item_pages_.size()); |
| } |
| + CHECK_LE(target_item_idx, app_items(target_page_idx)->item_count()); |
| + AddItemToPageAtIndex(item, target_page_idx, target_item_idx); |
| + } |
| +} |
| + |
| +// static |
| +size_t AppListModel::GetNumAppsPerPage() { |
| + return kPreferredCols * kPreferredRows; |
| +} |
| + |
| +size_t AppListModel::GetNumAppPages() const { |
| + return item_pages_.size(); |
| +} |
| + |
| +const AppListModel::AppItems& AppListModel::GetAppItemsForPage( |
| + size_t page_idx) const { |
| + CHECK_LT(page_idx, item_pages_.size()); |
| + return *(item_pages_[page_idx]->app_items()); |
| +} |
| + |
| +AppListModel::AppItems* AppListModel::app_items(size_t page_idx) { |
| + CHECK_LT(page_idx, item_pages_.size()); |
| + return item_pages_[page_idx]->app_items(); |
| +} |
| + |
| +void AppListModel::AddPage() { |
| + size_t page_idx = item_pages_.size(); |
| + item_pages_.push_back(new ItemPage(this, page_idx)); |
| + // Notify page added |
| +} |
| + |
| +void AppListModel::RemovePage(size_t page_idx) { |
| + item_pages_.erase(item_pages_.begin() + page_idx); |
| + // Notify page removed |
| +} |
| + |
| +size_t AppListModel::AddItemToPageAtIndex(AppListItemModel* item, |
| + size_t page_idx, size_t item_idx) { |
| + if (item_idx >= GetNumAppsPerPage()) { |
| + ++page_idx; |
| + item_idx = 0; |
| + } |
| + if (page_idx >= item_pages_.size()) |
| + AddPage(); |
| + |
| + AppItems* apps = app_items(page_idx); |
| + apps->AddAt(item_idx, item); |
| + VLOG(2) << "AddItemToPage: " << item->id() |
| + << " Page: " << page_idx << " Idx: " << item_idx |
| + << " / " << apps->item_count(); |
| + SetItemPosition(item, page_idx, item_idx); |
| + if (apps->item_count() > GetNumAppsPerPage()) { |
| + AppListItemModel* last_item = apps->RemoveAt(apps->item_count() - 1); |
| + CHECK_EQ(GetNumAppsPerPage(), apps->item_count()); |
| + if (++page_idx >= item_pages_.size()) |
| + AddPage(); |
| + AddItemToPageAtIndex(last_item, page_idx, 0); |
| + } |
| + return page_idx; |
| +} |
| + |
| +void AppListModel::SetItemPosition(AppListItemModel* item, |
| + size_t page_idx, size_t item_idx) { |
| + VLOG(2) << " SetItemPosition: " << item->id() |
| + << " Page: " << page_idx << " Idx: " << item_idx; |
| + AppItems* apps = app_items(page_idx); |
| + AppListItemModel* next = NULL; |
| + if (item_idx + 1 < apps->item_count()) { |
| + next = apps->GetItemAt(item_idx + 1); |
| + } else if (page_idx < item_pages_.size() - 1) { |
| + AppItems* next_apps = app_items(page_idx + 1); |
| + CHECK(next_apps->item_count() > 0); |
| + next = next_apps->GetItemAt(0); |
| + } |
| + AppListItemModel* prev = NULL; |
| + if (item_idx > 0) { |
| + prev = apps->GetItemAt(item_idx - 1); |
| + } else if (page_idx > 0) { |
| + AppItems* prev_apps = app_items(page_idx - 1); |
| + CHECK(prev_apps->item_count() > 0); |
| + prev = prev_apps->GetItemAt(prev_apps->item_count() - 1); |
| + } |
| + syncer::StringOrdinal new_position; |
| + if (prev) { |
| + new_position = next ? prev->position().CreateBetween(next->position()) |
| + : prev->position().CreateAfter(); |
| + } else { |
| + new_position = next ? next->position().CreateBefore() |
| + : syncer::StringOrdinal::CreateInitialOrdinal(); |
| } |
| + item->set_position(new_position); |
| } |
| } // namespace app_list |