diff options
Diffstat (limited to 'chrome/browser/ui/toolbar')
-rw-r--r-- | chrome/browser/ui/toolbar/back_forward_menu_model.cc | 379 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/back_forward_menu_model.h | 171 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc | 422 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/encoding_menu_controller.cc | 141 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/encoding_menu_controller.h | 56 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc | 93 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/toolbar_model.cc | 129 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/toolbar_model.h | 71 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/wrench_menu_model.cc | 536 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/wrench_menu_model.h | 150 | ||||
-rw-r--r-- | chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc | 105 |
11 files changed, 2253 insertions, 0 deletions
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.cc b/chrome/browser/ui/toolbar/back_forward_menu_model.cc new file mode 100644 index 0000000..390dce0 --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model.cc @@ -0,0 +1,379 @@ +// Copyright (c) 2010 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 "build/build_config.h" + +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" + +#include "app/l10n_util.h" +#include "app/text_elider.h" +#include "app/resource_bundle.h" +#include "base/string_number_conversions.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "net/base/registry_controlled_domain.h" + +const int BackForwardMenuModel::kMaxHistoryItems = 12; +const int BackForwardMenuModel::kMaxChapterStops = 5; +static const int kMaxWidth = 700; + +BackForwardMenuModel::BackForwardMenuModel(Browser* browser, + ModelType model_type) + : browser_(browser), + test_tab_contents_(NULL), + model_type_(model_type) { +} + +bool BackForwardMenuModel::HasIcons() const { + return true; +} + +int BackForwardMenuModel::GetItemCount() const { + int items = GetHistoryItemCount(); + + if (items > 0) { + int chapter_stops = 0; + + // Next, we count ChapterStops, if any. + if (items == kMaxHistoryItems) + chapter_stops = GetChapterStopCount(items); + + if (chapter_stops) + items += chapter_stops + 1; // Chapter stops also need a separator. + + // If the menu is not empty, add two positions in the end + // for a separator and a "Show Full History" item. + items += 2; + } + + return items; +} + +menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { + return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; +} + +int BackForwardMenuModel::GetCommandIdAt(int index) const { + return index; +} + +string16 BackForwardMenuModel::GetLabelAt(int index) const { + // Return label "Show Full History" for the last item of the menu. + if (index == GetItemCount() - 1) + return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); + + // Return an empty string for a separator. + if (IsSeparator(index)) + return string16(); + + // Return the entry title, escaping any '&' characters and eliding it if it's + // super long. + NavigationEntry* entry = GetNavigationEntry(index); + string16 menu_text(entry->GetTitleForDisplay( + &GetTabContents()->controller())); + menu_text = gfx::ElideText(menu_text, gfx::Font(), kMaxWidth, false); + + for (size_t i = menu_text.find('&'); i != string16::npos; + i = menu_text.find('&', i + 2)) { + menu_text.insert(i, 1, '&'); + } + return menu_text; +} + +bool BackForwardMenuModel::IsItemDynamicAt(int index) const { + // This object is only used for a single showing of a menu. + return false; +} + +bool BackForwardMenuModel::GetAcceleratorAt( + int index, + menus::Accelerator* accelerator) const { + return false; +} + +bool BackForwardMenuModel::IsItemCheckedAt(int index) const { + return false; +} + +int BackForwardMenuModel::GetGroupIdAt(int index) const { + return false; +} + +bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { + if (!ItemHasIcon(index)) + return false; + + if (index == GetItemCount() - 1) { + *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_HISTORY_FAVICON); + } else { + NavigationEntry* entry = GetNavigationEntry(index); + *icon = entry->favicon().bitmap(); + } + + return true; +} + +menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( + int index) const { + return NULL; +} + +bool BackForwardMenuModel::IsEnabledAt(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { + return NULL; +} + +void BackForwardMenuModel::HighlightChangedTo(int index) { +} + +void BackForwardMenuModel::ActivatedAt(int index) { + ActivatedAtWithDisposition(index, CURRENT_TAB); +} + +void BackForwardMenuModel::ActivatedAtWithDisposition( + int index, int disposition) { + Profile* profile = browser_->profile(); + + DCHECK(!IsSeparator(index)); + + // Execute the command for the last item: "Show Full History". + if (index == GetItemCount() - 1) { + UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), + profile); + browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL), false); + return; + } + + // Log whether it was a history or chapter click. + if (index < GetHistoryItemCount()) { + UserMetrics::RecordComputedAction( + BuildActionName("HistoryClick", index), profile); + } else { + UserMetrics::RecordComputedAction( + BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), + profile); + } + + int controller_index = MenuIndexToNavEntryIndex(index); + if (!browser_->NavigateToIndexWithDisposition( + controller_index, static_cast<WindowOpenDisposition>(disposition))) { + NOTREACHED(); + } +} + +void BackForwardMenuModel::MenuWillShow() { + UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), + browser_->profile()); +} + +bool BackForwardMenuModel::IsSeparator(int index) const { + int history_items = GetHistoryItemCount(); + // If the index is past the number of history items + separator, + // we then consider if it is a chapter-stop entry. + if (index > history_items) { + // We either are in ChapterStop area, or at the end of the list (the "Show + // Full History" link). + int chapter_stops = GetChapterStopCount(history_items); + if (chapter_stops == 0) + return false; // We must have reached the "Show Full History" link. + // Otherwise, look to see if we have reached the separator for the + // chapter-stops. If not, this is a chapter stop. + return (index == history_items + 1 + chapter_stops); + } + + // Look to see if we have reached the separator for the history items. + return index == history_items; +} + +int BackForwardMenuModel::GetHistoryItemCount() const { + TabContents* contents = GetTabContents(); + int items = 0; + + if (model_type_ == FORWARD_MENU) { + // Only count items from n+1 to end (if n is current entry) + items = contents->controller().entry_count() - + contents->controller().GetCurrentEntryIndex() - 1; + } else { + items = contents->controller().GetCurrentEntryIndex(); + } + + if (items > kMaxHistoryItems) + items = kMaxHistoryItems; + else if (items < 0) + items = 0; + + return items; +} + +int BackForwardMenuModel::GetChapterStopCount(int history_items) const { + TabContents* contents = GetTabContents(); + + int chapter_stops = 0; + int current_entry = contents->controller().GetCurrentEntryIndex(); + + if (history_items == kMaxHistoryItems) { + int chapter_id = current_entry; + if (model_type_ == FORWARD_MENU) { + chapter_id += history_items; + } else { + chapter_id -= history_items; + } + + do { + chapter_id = GetIndexOfNextChapterStop(chapter_id, + model_type_ == FORWARD_MENU); + if (chapter_id != -1) + ++chapter_stops; + } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); + } + + return chapter_stops; +} + +int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, + bool forward) const { + TabContents* contents = GetTabContents(); + NavigationController& controller = contents->controller(); + + int max_count = controller.entry_count(); + if (start_from < 0 || start_from >= max_count) + return -1; // Out of bounds. + + if (forward) { + if (start_from < max_count - 1) { + // We want to advance over the current chapter stop, so we add one. + // We don't need to do this when direction is backwards. + start_from++; + } else { + return -1; + } + } + + NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); + const GURL& url = start_entry->url(); + + if (!forward) { + // When going backwards we return the first entry we find that has a + // different domain. + for (int i = start_from - 1; i >= 0; --i) { + if (!net::RegistryControlledDomainService::SameDomainOrHost(url, + controller.GetEntryAtIndex(i)->url())) + return i; + } + // We have reached the beginning without finding a chapter stop. + return -1; + } else { + // When going forwards we return the entry before the entry that has a + // different domain. + for (int i = start_from + 1; i < max_count; ++i) { + if (!net::RegistryControlledDomainService::SameDomainOrHost(url, + controller.GetEntryAtIndex(i)->url())) + return i - 1; + } + // Last entry is always considered a chapter stop. + return max_count - 1; + } +} + +int BackForwardMenuModel::FindChapterStop(int offset, + bool forward, + int skip) const { + if (offset < 0 || skip < 0) + return -1; + + if (!forward) + offset *= -1; + + TabContents* contents = GetTabContents(); + int entry = contents->controller().GetCurrentEntryIndex() + offset; + for (int i = 0; i < skip + 1; i++) + entry = GetIndexOfNextChapterStop(entry, forward); + + return entry; +} + +bool BackForwardMenuModel::ItemHasCommand(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +bool BackForwardMenuModel::ItemHasIcon(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { + return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); +} + +TabContents* BackForwardMenuModel::GetTabContents() const { + // We use the test tab contents if the unit test has specified it. + return test_tab_contents_ ? test_tab_contents_ : + browser_->GetSelectedTabContents(); +} + +int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { + TabContents* contents = GetTabContents(); + int history_items = GetHistoryItemCount(); + + DCHECK_GE(index, 0); + + // Convert anything above the History items separator. + if (index < history_items) { + if (model_type_ == FORWARD_MENU) { + index += contents->controller().GetCurrentEntryIndex() + 1; + } else { + // Back menu is reverse. + index = contents->controller().GetCurrentEntryIndex() - (index + 1); + } + return index; + } + if (index == history_items) + return -1; // Don't translate the separator for history items. + + if (index >= history_items + 1 + GetChapterStopCount(history_items)) + return -1; // This is beyond the last chapter stop so we abort. + + // This menu item is a chapter stop located between the two separators. + index = FindChapterStop(history_items, + model_type_ == FORWARD_MENU, + index - history_items - 1); + + return index; +} + +NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { + int controller_index = MenuIndexToNavEntryIndex(index); + NavigationController& controller = GetTabContents()->controller(); + if (controller_index >= 0 && controller_index < controller.entry_count()) + return controller.GetEntryAtIndex(controller_index); + + NOTREACHED(); + return NULL; +} + +std::string BackForwardMenuModel::BuildActionName( + const std::string& action, int index) const { + DCHECK(!action.empty()); + DCHECK(index >= -1); + std::string metric_string; + if (model_type_ == FORWARD_MENU) + metric_string += "ForwardMenu_"; + else + metric_string += "BackMenu_"; + metric_string += action; + if (index != -1) { + // +1 is for historical reasons (indices used to start at 1). + metric_string += base::IntToString(index + 1); + } + return metric_string; +} diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.h b/chrome/browser/ui/toolbar/back_forward_menu_model.h new file mode 100644 index 0000000..1d669a0 --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model.h @@ -0,0 +1,171 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ +#pragma once + +#include <string> + +#include "app/menus/menu_model.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "webkit/glue/window_open_disposition.h" + +class Browser; +class SkBitmap; +class TabContents; +class NavigationEntry; + +/////////////////////////////////////////////////////////////////////////////// +// +// BackForwardMenuModel +// +// Interface for the showing of the dropdown menu for the Back/Forward buttons. +// Actual implementations are platform-specific. +/////////////////////////////////////////////////////////////////////////////// +class BackForwardMenuModel : public menus::MenuModel { + public: + // These are IDs used to identify individual UI elements within the + // browser window using View::GetViewByID. + enum ModelType { + FORWARD_MENU = 1, + BACKWARD_MENU = 2 + }; + + BackForwardMenuModel(Browser* browser, ModelType model_type); + virtual ~BackForwardMenuModel() { } + + // MenuModel implementation. + virtual bool HasIcons() const; + // Returns how many items the menu should show, including history items, + // chapter-stops, separators and the Show Full History link. This function + // uses GetHistoryItemCount() and GetChapterStopCount() internally to figure + // out the total number of items to show. + virtual int GetItemCount() const; + virtual ItemType GetTypeAt(int index) const; + virtual int GetCommandIdAt(int index) const; + virtual string16 GetLabelAt(int index) const; + virtual bool IsItemDynamicAt(int index) const; + virtual bool GetAcceleratorAt(int index, + menus::Accelerator* accelerator) const; + virtual bool IsItemCheckedAt(int index) const; + virtual int GetGroupIdAt(int index) const; + virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const; + virtual bool IsEnabledAt(int index) const; + virtual MenuModel* GetSubmenuModelAt(int index) const; + virtual void HighlightChangedTo(int index); + virtual void ActivatedAt(int index); + virtual void ActivatedAtWithDisposition(int index, int disposition); + virtual void MenuWillShow(); + + // Is the item at |index| a separator? + bool IsSeparator(int index) const; + + private: + // Allows the unit test to use its own dummy tab contents. + void set_test_tab_contents(TabContents* test_tab_contents) { + test_tab_contents_ = test_tab_contents; + } + + // Returns how many history items the menu should show. For example, if the + // navigation controller of the current tab has a current entry index of 5 and + // forward_direction_ is false (we are the back button delegate) then this + // function will return 5 (representing 0-4). If forward_direction_ is + // true (we are the forward button delegate), then this function will return + // the number of entries after 5. Note, though, that in either case it will + // not report more than kMaxHistoryItems. The number returned also does not + // include the separator line after the history items (nor the separator for + // the "Show Full History" link). + int GetHistoryItemCount() const; + + // Returns how many chapter-stop items the menu should show. For the + // definition of a chapter-stop, see GetIndexOfNextChapterStop(). The number + // returned does not include the separator lines before and after the + // chapter-stops. + int GetChapterStopCount(int history_items) const; + + // Finds the next chapter-stop in the NavigationEntryList starting from + // the index specified in |start_from| and continuing in the direction + // specified (|forward|) until either a chapter-stop is found or we reach the + // end, in which case -1 is returned. If |start_from| is out of bounds, -1 + // will also be returned. A chapter-stop is defined as the last page the user + // browsed to within the same domain. For example, if the user's homepage is + // Google and she navigates to Google pages G1, G2 and G3 before heading over + // to WikiPedia for pages W1 and W2 and then back to Google for pages G4 and + // G5 then G3, W2 and G5 are considered chapter-stops. The return value from + // this function is an index into the NavigationEntryList vector. + int GetIndexOfNextChapterStop(int start_from, bool forward) const; + + // Finds a given chapter-stop starting at the currently active entry in the + // NavigationEntryList vector advancing first forward or backward by |offset| + // (depending on the direction specified in parameter |forward|). It also + // allows you to skip chapter-stops by specifying a positive value for |skip|. + // Example: FindChapterStop(5, false, 3) starts with the currently active + // index, subtracts 5 from it and then finds the fourth chapter-stop before + // that index (skipping the first 3 it finds). + // Example: FindChapterStop(0, true, 0) is functionally equivalent to + // calling GetIndexOfNextChapterStop(GetCurrentEntryIndex(), true). + // + // NOTE: Both |offset| and |skip| must be non-negative. The return value from + // this function is an index into the NavigationEntryList vector. If |offset| + // is out of bounds or if we skip too far (run out of chapter-stops) this + // function returns -1. + int FindChapterStop(int offset, bool forward, int skip) const; + + // How many items (max) to show in the back/forward history menu dropdown. + static const int kMaxHistoryItems; + + // How many chapter-stops (max) to show in the back/forward dropdown list. + static const int kMaxChapterStops; + + // Takes a menu item index as passed in through one of the menu delegate + // functions and converts it into an index into the NavigationEntryList + // vector. |index| can point to a separator, or the + // "Show Full History" link in which case this function returns -1. + int MenuIndexToNavEntryIndex(int index) const; + + // Does the item have a command associated with it? + bool ItemHasCommand(int index) const; + + // Returns true if there is an icon for this menu item. + bool ItemHasIcon(int index) const; + + // Allow the unit test to use the "Show Full History" label. + string16 GetShowFullHistoryLabel() const; + + // Looks up a NavigationEntry by menu id. + NavigationEntry* GetNavigationEntry(int index) const; + + // Retrieves the TabContents pointer to use, which is either the one that + // the unit test sets (using SetTabContentsForUnitTest) or the one from + // the browser window. + TabContents* GetTabContents() const; + + // Build a string version of a user action on this menu, used as an + // identifier for logging user behavior. + // E.g. BuildActionName("Click", 2) returns "BackMenu_Click2". + // An index of -1 means no index. + std::string BuildActionName(const std::string& name, int index) const; + + Browser* browser_; + + // The unit tests will provide their own TabContents to use. + TabContents* test_tab_contents_; + + // Represents whether this is the delegate for the forward button or the + // back button. + ModelType model_type_; + + friend class BackFwdMenuModelTest; + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, BasicCase); + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, MaxItemsTest); + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, ChapterStops); + + DISALLOW_COPY_AND_ASSIGN(BackForwardMenuModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc new file mode 100644 index 0000000..2e4b988 --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc @@ -0,0 +1,422 @@ +// Copyright (c) 2010 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 "chrome/browser/ui/toolbar/back_forward_menu_model.h" + +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/test_tab_contents.h" +#include "chrome/common/url_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +class BackFwdMenuModelTest : public RenderViewHostTestHarness { + public: + void ValidateModel(BackForwardMenuModel* model, int history_items, + int chapter_stops) { + int h = std::min(BackForwardMenuModel::kMaxHistoryItems, history_items); + int c = std::min(BackForwardMenuModel::kMaxChapterStops, chapter_stops); + EXPECT_EQ(h, model->GetHistoryItemCount()); + EXPECT_EQ(c, model->GetChapterStopCount(h)); + if (h > 0) + h += 2; // Separator and View History link. + if (c > 0) + ++c; + EXPECT_EQ(h + c, model->GetItemCount()); + } + + void LoadURLAndUpdateState(const char* url, const char* title) { + NavigateAndCommit(GURL(url)); + controller().GetLastCommittedEntry()->set_title(UTF8ToUTF16(title)); + } + + // Navigate back or forward the given amount and commits the entry (which + // will be pending after we ask to navigate there). + void NavigateToOffset(int offset) { + controller().GoToOffset(offset); + contents()->CommitPendingNavigation(); + } + + // Same as NavigateToOffset but goes to an absolute index. + void NavigateToIndex(int index) { + controller().GoToIndex(index); + contents()->CommitPendingNavigation(); + } + + // Goes back/forward and commits the load. + void GoBack() { + controller().GoBack(); + contents()->CommitPendingNavigation(); + } + void GoForward() { + controller().GoForward(); + contents()->CommitPendingNavigation(); + } +}; + +TEST_F(BackFwdMenuModelTest, BasicCase) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_FALSE(back_model->ItemHasCommand(1)); + + // Seed the controller with a few URLs + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + + // There're two more items here: a separator and a "Show Full History". + EXPECT_EQ(9, back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("C2"), back_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(6)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(8)); + + EXPECT_TRUE(back_model->ItemHasCommand(0)); + EXPECT_TRUE(back_model->ItemHasCommand(6)); + EXPECT_TRUE(back_model->IsSeparator(7)); + EXPECT_TRUE(back_model->ItemHasCommand(8)); + EXPECT_FALSE(back_model->ItemHasCommand(9)); + EXPECT_FALSE(back_model->ItemHasCommand(9)); + + NavigateToOffset(-7); + + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(9, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(6)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(8)); + + EXPECT_TRUE(forward_model->ItemHasCommand(0)); + EXPECT_TRUE(forward_model->ItemHasCommand(6)); + EXPECT_TRUE(forward_model->IsSeparator(7)); + EXPECT_TRUE(forward_model->ItemHasCommand(8)); + EXPECT_FALSE(forward_model->ItemHasCommand(7)); + EXPECT_FALSE(forward_model->ItemHasCommand(9)); + + NavigateToOffset(4); + + EXPECT_EQ(6, back_model->GetItemCount()); + EXPECT_EQ(5, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("B1"), back_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(3)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(5)); + EXPECT_EQ(ASCIIToUTF16("C1"), forward_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(2)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(4)); +} + +TEST_F(BackFwdMenuModelTest, MaxItemsTest) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + // Seed the controller with 32 URLs + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + LoadURLAndUpdateState("http://www.b.com/3", "B3"); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + LoadURLAndUpdateState("http://www.d.com/1", "D1"); + LoadURLAndUpdateState("http://www.d.com/2", "D2"); + LoadURLAndUpdateState("http://www.d.com/3", "D3"); + LoadURLAndUpdateState("http://www.e.com/1", "E1"); + LoadURLAndUpdateState("http://www.e.com/2", "E2"); + LoadURLAndUpdateState("http://www.e.com/3", "E3"); + LoadURLAndUpdateState("http://www.f.com/1", "F1"); + LoadURLAndUpdateState("http://www.f.com/2", "F2"); + LoadURLAndUpdateState("http://www.f.com/3", "F3"); + LoadURLAndUpdateState("http://www.g.com/1", "G1"); + LoadURLAndUpdateState("http://www.g.com/2", "G2"); + LoadURLAndUpdateState("http://www.g.com/3", "G3"); + LoadURLAndUpdateState("http://www.h.com/1", "H1"); + LoadURLAndUpdateState("http://www.h.com/2", "H2"); + LoadURLAndUpdateState("http://www.h.com/3", "H3"); + LoadURLAndUpdateState("http://www.i.com/1", "I1"); + LoadURLAndUpdateState("http://www.i.com/2", "I2"); + LoadURLAndUpdateState("http://www.i.com/3", "I3"); + LoadURLAndUpdateState("http://www.j.com/1", "J1"); + LoadURLAndUpdateState("http://www.j.com/2", "J2"); + LoadURLAndUpdateState("http://www.j.com/3", "J3"); + LoadURLAndUpdateState("http://www.k.com/1", "K1"); + LoadURLAndUpdateState("http://www.k.com/2", "K2"); + + // Also there're two more for a separator and a "Show Full History". + int chapter_stop_offset = 6; + EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset, + back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("K1"), back_model->GetLabelAt(0)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 + + chapter_stop_offset)); + + // Test for out of bounds (beyond Show Full History). + EXPECT_FALSE(back_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems + chapter_stop_offset + 2)); + + EXPECT_TRUE(back_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems - 1)); + EXPECT_TRUE(back_model->IsSeparator( + BackForwardMenuModel::kMaxHistoryItems)); + + NavigateToIndex(0); + + EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset, + forward_model->GetItemCount()); + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 + + chapter_stop_offset)); + + // Out of bounds + EXPECT_FALSE(forward_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset)); + + EXPECT_TRUE(forward_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems - 1)); + EXPECT_TRUE(forward_model->IsSeparator( + BackForwardMenuModel::kMaxHistoryItems)); +} + +TEST_F(BackFwdMenuModelTest, ChapterStops) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + // Seed the controller with 32 URLs. + int i = 0; + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + ValidateModel(back_model.get(), i++, 0); + // i = 5 + LoadURLAndUpdateState("http://www.b.com/3", "B3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.d.com/1", "D1"); + ValidateModel(back_model.get(), i++, 0); + // i = 10 + LoadURLAndUpdateState("http://www.d.com/2", "D2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.d.com/3", "D3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/1", "E1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/2", "E2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/3", "E3"); + ValidateModel(back_model.get(), i++, 0); + // i = 15 + LoadURLAndUpdateState("http://www.f.com/1", "F1"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.f.com/2", "F2"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.f.com/3", "F3"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.g.com/1", "G1"); + ValidateModel(back_model.get(), i++, 2); + LoadURLAndUpdateState("http://www.g.com/2", "G2"); + ValidateModel(back_model.get(), i++, 2); + // i = 20 + LoadURLAndUpdateState("http://www.g.com/3", "G3"); + ValidateModel(back_model.get(), i++, 2); + LoadURLAndUpdateState("http://www.h.com/1", "H1"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.h.com/2", "H2"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.h.com/3", "H3"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.i.com/1", "I1"); + ValidateModel(back_model.get(), i++, 4); + // i = 25 + LoadURLAndUpdateState("http://www.i.com/2", "I2"); + ValidateModel(back_model.get(), i++, 4); + LoadURLAndUpdateState("http://www.i.com/3", "I3"); + ValidateModel(back_model.get(), i++, 4); + LoadURLAndUpdateState("http://www.j.com/1", "J1"); + ValidateModel(back_model.get(), i++, 5); + LoadURLAndUpdateState("http://www.j.com/2", "J2"); + ValidateModel(back_model.get(), i++, 5); + LoadURLAndUpdateState("http://www.j.com/3", "J3"); + ValidateModel(back_model.get(), i++, 5); + // i = 30 + LoadURLAndUpdateState("http://www.k.com/1", "K1"); + ValidateModel(back_model.get(), i++, 6); + LoadURLAndUpdateState("http://www.k.com/2", "K2"); + ValidateModel(back_model.get(), i++, 6); + // i = 32 + LoadURLAndUpdateState("http://www.k.com/3", "K3"); + ValidateModel(back_model.get(), i++, 6); + + // A chapter stop is defined as the last page the user + // browsed to within the same domain. + + // Check to see if the chapter stops have the right labels. + int index = BackForwardMenuModel::kMaxHistoryItems; + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("F3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("E3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("D3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("C3"), back_model->GetLabelAt(index++)); + // The menu should only show a maximum of 5 chapter stops. + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index + 1)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(index + 2)); + + // If we go back two we should still see the same chapter stop at the end. + GoBack(); + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + // But if we go back again, it should change. + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + // It is now a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index)); + // Undo our position change. + NavigateToOffset(6); + + // Go back enough to make sure no chapter stops should appear. + NavigateToOffset(-BackForwardMenuModel::kMaxHistoryItems); + ValidateModel(forward_model.get(), BackForwardMenuModel::kMaxHistoryItems, 0); + // Go forward (still no chapter stop) + GoForward(); + ValidateModel(forward_model.get(), + BackForwardMenuModel::kMaxHistoryItems - 1, 0); + // Go back two (one chapter stop should show up) + GoBack(); + GoBack(); + ValidateModel(forward_model.get(), + BackForwardMenuModel::kMaxHistoryItems, 1); + + // Go to beginning. + NavigateToIndex(0); + + // Check to see if the chapter stops have the right labels. + index = BackForwardMenuModel::kMaxHistoryItems; + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("E3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("F3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("G3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("H3"), forward_model->GetLabelAt(index++)); + // The menu should only show a maximum of 5 chapter stops. + EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index)); + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), forward_model->GetLabelAt(index + 1)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(index + 2)); + + // If we advance one we should still see the same chapter stop at the end. + GoForward(); + EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index)); + // But if we advance one again, it should change. + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("K3"), forward_model->GetLabelAt(index)); + + // Now test the boundary cases by using the chapter stop function directly. + // Out of bounds, first too far right (incrementing), then too far left. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(33, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(-1, true)); + // Test being at end and going right, then at beginning going left. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(0, false)); + // Test success: beginning going right and end going left. + EXPECT_EQ(2, back_model->GetIndexOfNextChapterStop(0, true)); + EXPECT_EQ(29, back_model->GetIndexOfNextChapterStop(32, false)); + // Now see when the chapter stops begin to show up. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(2, false)); + EXPECT_EQ(2, back_model->GetIndexOfNextChapterStop(3, false)); + // Now see when the chapter stops end. + EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(30, true)); + EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(31, true)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true)); + + // Bug found during review (two different sites, but first wasn't considered + // a chapter-stop). + // Go to A1; + NavigateToIndex(0); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + EXPECT_EQ(0, back_model->GetIndexOfNextChapterStop(1, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true)); + + // Now see if it counts 'www.x.com' and 'mail.x.com' as same domain, which + // it should. + // Go to A1. + NavigateToIndex(0); + LoadURLAndUpdateState("http://mail.a.com/2", "A2-mai"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://mail.b.com/2", "B2-mai"); + LoadURLAndUpdateState("http://new.site.com", "new"); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true)); + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(1, true)); + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(2, true)); + EXPECT_EQ(4, back_model->GetIndexOfNextChapterStop(3, true)); + // And try backwards as well. + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(4, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(3, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(2, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false)); +} diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller.cc b/chrome/browser/ui/toolbar/encoding_menu_controller.cc new file mode 100644 index 0000000..c93fb1e --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2009 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 "chrome/browser/ui/toolbar/encoding_menu_controller.h" + +#include "app/l10n_util.h" +#include "base/i18n/rtl.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" +#include "grit/generated_resources.h" + +const int EncodingMenuController::kValidEncodingIds[] = { + IDC_ENCODING_UTF8, + IDC_ENCODING_UTF16LE, + IDC_ENCODING_ISO88591, + IDC_ENCODING_WINDOWS1252, + IDC_ENCODING_GBK, + IDC_ENCODING_GB18030, + IDC_ENCODING_BIG5, + IDC_ENCODING_BIG5HKSCS, + IDC_ENCODING_KOREAN, + IDC_ENCODING_SHIFTJIS, + IDC_ENCODING_ISO2022JP, + IDC_ENCODING_EUCJP, + IDC_ENCODING_THAI, + IDC_ENCODING_ISO885915, + IDC_ENCODING_MACINTOSH, + IDC_ENCODING_ISO88592, + IDC_ENCODING_WINDOWS1250, + IDC_ENCODING_ISO88595, + IDC_ENCODING_WINDOWS1251, + IDC_ENCODING_KOI8R, + IDC_ENCODING_KOI8U, + IDC_ENCODING_ISO88597, + IDC_ENCODING_WINDOWS1253, + IDC_ENCODING_ISO88594, + IDC_ENCODING_ISO885913, + IDC_ENCODING_WINDOWS1257, + IDC_ENCODING_ISO88593, + IDC_ENCODING_ISO885910, + IDC_ENCODING_ISO885914, + IDC_ENCODING_ISO885916, + IDC_ENCODING_WINDOWS1254, + IDC_ENCODING_ISO88596, + IDC_ENCODING_WINDOWS1256, + IDC_ENCODING_ISO88598, + IDC_ENCODING_WINDOWS1255, + IDC_ENCODING_WINDOWS1258, + IDC_ENCODING_ISO88598I, +}; + +bool EncodingMenuController::DoesCommandBelongToEncodingMenu(int id) { + if (id == IDC_ENCODING_AUTO_DETECT) { + return true; + } + + for (size_t i = 0; i < arraysize(kValidEncodingIds); ++i) { + if (id == kValidEncodingIds[i]) { + return true; + } + } + + return false; +} + +const int* EncodingMenuController::ValidGUIEncodingIDs() { + return kValidEncodingIds; +} + +int EncodingMenuController::NumValidGUIEncodingIDs() { + return arraysize(kValidEncodingIds); +} + +bool EncodingMenuController::IsItemChecked( + Profile* browser_profile, + const std::string& current_tab_encoding, + int item_id) { + if (!DoesCommandBelongToEncodingMenu(item_id)) + return false; + + std::string encoding = current_tab_encoding; + if (encoding.empty()) + encoding = browser_profile->GetPrefs()->GetString(prefs::kDefaultCharset); + + if (item_id == IDC_ENCODING_AUTO_DETECT) { + return browser_profile->GetPrefs()->GetBoolean( + prefs::kWebKitUsesUniversalDetector); + } + + if (!encoding.empty()) { + return encoding == + CharacterEncoding::GetCanonicalEncodingNameByCommandId(item_id); + } + + return false; +} + +void EncodingMenuController::GetEncodingMenuItems(Profile* profile, + EncodingMenuItemList* menuItems) { + + DCHECK(menuItems); + EncodingMenuItem separator(0, string16()); + + menuItems->clear(); + menuItems->push_back( + EncodingMenuItem(IDC_ENCODING_AUTO_DETECT, + l10n_util::GetStringUTF16(IDS_ENCODING_AUTO_DETECT))); + menuItems->push_back(separator); + + // Create current display encoding list. + const std::vector<CharacterEncoding::EncodingInfo>* encodings; + + // Build the list of encoding ids : It is made of the + // locale-dependent short list, the cache of recently selected + // encodings and other encodings. + encodings = CharacterEncoding::GetCurrentDisplayEncodings( + g_browser_process->GetApplicationLocale(), + profile->GetPrefs()->GetString(prefs::kStaticEncodings), + profile->GetPrefs()->GetString(prefs::kRecentlySelectedEncoding)); + DCHECK(encodings); + DCHECK(!encodings->empty()); + + // Build up output list for menu. + std::vector<CharacterEncoding::EncodingInfo>::const_iterator it; + for (it = encodings->begin(); it != encodings->end(); ++it) { + if (it->encoding_id) { + std::wstring encoding = it->encoding_display_name; + base::i18n::AdjustStringForLocaleDirection(&encoding); + menuItems->push_back(EncodingMenuItem(it->encoding_id, + WideToUTF16(encoding))); + } else { + menuItems->push_back(separator); + } + } +} diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller.h b/chrome/browser/ui/toolbar/encoding_menu_controller.h new file mode 100644 index 0000000..bfa8c57 --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller.h @@ -0,0 +1,56 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ +#define CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ +#pragma once + +#include <utility> +#include <string> +#include <vector> + +#include "base/basictypes.h" // For DISALLOW_COPY_AND_ASSIGN +#include "base/gtest_prod_util.h" +#include "base/string16.h" + +class Profile; + +// Cross-platform logic needed for the encoding menu. +// For now, we don't need to track state so all methods are static. +class EncodingMenuController { + FRIEND_TEST_ALL_PREFIXES(EncodingMenuControllerTest, EncodingIDsBelongTest); + FRIEND_TEST_ALL_PREFIXES(EncodingMenuControllerTest, IsItemChecked); + + public: + typedef std::pair<int, string16> EncodingMenuItem; + typedef std::vector<EncodingMenuItem> EncodingMenuItemList; + + public: + EncodingMenuController() {} + + // Given a command ID, does this command belong to the encoding menu? + bool DoesCommandBelongToEncodingMenu(int id); + + // Returns true if the given encoding menu item (specified by item_id) + // is checked. Note that this header is included from objc, where the name + // "id" is reserved. + bool IsItemChecked(Profile* browser_profile, + const std::string& current_tab_encoding, + int item_id); + + // Fills in a list of menu items in the order they should appear in the menu. + // Items whose ids are 0 are separators. + void GetEncodingMenuItems(Profile* profile, + EncodingMenuItemList* menuItems); + + private: + // List of all valid encoding GUI IDs. + static const int kValidEncodingIds[]; + const int* ValidGUIEncodingIDs(); + int NumValidGUIEncodingIDs(); + + DISALLOW_COPY_AND_ASSIGN(EncodingMenuController); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc b/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc new file mode 100644 index 0000000..13fa63c --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2009 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 "chrome/browser/ui/toolbar/encoding_menu_controller.h" + +#include <string> + +#include "base/basictypes.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + + +class EncodingMenuControllerTest : public testing::Test { +}; + +TEST_F(EncodingMenuControllerTest, EncodingIDsBelongTest) { + EncodingMenuController controller; + + // Check some bogus ids to make sure they're never valid. + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(0)); + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(-1)); + + int num_valid_encoding_ids = controller.NumValidGUIEncodingIDs(); + const int* valid_encodings = controller.ValidGUIEncodingIDs(); + ASSERT_TRUE(controller.DoesCommandBelongToEncodingMenu( + IDC_ENCODING_AUTO_DETECT)); + // Check that all valid encodings are accepted. + for (int i = 0; i < num_valid_encoding_ids; ++i) { + ASSERT_TRUE(controller.DoesCommandBelongToEncodingMenu(valid_encodings[i])); + } + + // This test asserts that we haven't added a new valid ID without including it + // in the kValidEncodingIds test list above. + // The premise is that new encodings will be added directly after the current + // ones so we'll catch such cases. + int one_past_largest_id = valid_encodings[num_valid_encoding_ids - 1] + 1; + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(one_past_largest_id)); +} + +TEST_F(EncodingMenuControllerTest, ListEncodingMenuItems) { + typedef EncodingMenuController::EncodingMenuItemList EncodingMenuItemList; + EncodingMenuController controller; + + EncodingMenuItemList english_items; + TestingProfile profile_en; + + controller.GetEncodingMenuItems(&profile_en, &english_items); + + // Make sure there are items in the lists. + ASSERT_TRUE(english_items.size() > 0); + // Make sure that autodetect is the first item on both menus + ASSERT_EQ(english_items[0].first, IDC_ENCODING_AUTO_DETECT); +} + +TEST_F(EncodingMenuControllerTest, IsItemChecked) { + TestingProfile profile_en; + std::string encoding("UTF-8"); + + // Check that enabling and disabling autodetect works. + bool autodetect_enabed[] = {true, false}; + PrefService* prefs = profile_en.GetPrefs(); + EncodingMenuController controller; + for (size_t i = 0; i < arraysize(autodetect_enabed); ++i) { + bool enabled = autodetect_enabed[i]; + prefs->SetBoolean(prefs::kWebKitUsesUniversalDetector, enabled); + ASSERT_TRUE(controller.IsItemChecked(&profile_en, + encoding, + IDC_ENCODING_AUTO_DETECT) == enabled); + } + + // Check all valid encodings, make sure only one is enabled when autodetection + // is turned off. + prefs->SetBoolean(prefs::kWebKitUsesUniversalDetector, false); + bool encoding_is_enabled = false; + size_t num_valid_encoding_ids = controller.NumValidGUIEncodingIDs(); + const int* valid_encodings = controller.ValidGUIEncodingIDs(); + for (size_t i = 0; i < num_valid_encoding_ids; ++i) { + bool on = controller.IsItemChecked(&profile_en, + encoding, + valid_encodings[i]); + // Only one item in the encoding menu can be selected at a time. + ASSERT_FALSE(on && encoding_is_enabled); + encoding_is_enabled |= on; + } + + // Make sure at least one encoding is enabled. + ASSERT_TRUE(encoding_is_enabled); +} diff --git a/chrome/browser/ui/toolbar/toolbar_model.cc b/chrome/browser/ui/toolbar/toolbar_model.cc new file mode 100644 index 0000000..5a6a97c --- /dev/null +++ b/chrome/browser/ui/toolbar/toolbar_model.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2010 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 "chrome/browser/ui/toolbar/toolbar_model.h" + +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ssl/ssl_error_info.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "net/base/cert_status_flags.h" +#include "net/base/net_util.h" + +ToolbarModel::ToolbarModel(Browser* browser) + : browser_(browser), + input_in_progress_(false) { +} + +ToolbarModel::~ToolbarModel() { +} + +// ToolbarModel Implementation. +std::wstring ToolbarModel::GetText() const { + GURL url(chrome::kAboutBlankURL); + std::string languages; // Empty if we don't have a |navigation_controller|. + + NavigationController* navigation_controller = GetNavigationController(); + if (navigation_controller) { + languages = navigation_controller->profile()->GetPrefs()->GetString( + prefs::kAcceptLanguages); + NavigationEntry* entry = navigation_controller->GetActiveEntry(); + if (!navigation_controller->tab_contents()->ShouldDisplayURL()) { + // Explicitly hide the URL for this tab. + url = GURL(); + } else if (entry) { + url = entry->virtual_url(); + } + } + if (url.spec().length() > chrome::kMaxURLDisplayChars) + url = url.IsStandard() ? url.GetOrigin() : GURL(url.scheme() + ":"); + // Note that we can't unescape spaces here, because if the user copies this + // and pastes it into another program, that program may think the URL ends at + // the space. + return AutocompleteInput::FormattedStringWithEquivalentMeaning(url, + UTF16ToWideHack(net::FormatUrl(url, languages, net::kFormatUrlOmitAll, + UnescapeRule::NORMAL, NULL, NULL, NULL))); +} + +ToolbarModel::SecurityLevel ToolbarModel::GetSecurityLevel() const { + if (input_in_progress_) // When editing, assume no security style. + return NONE; + + NavigationController* navigation_controller = GetNavigationController(); + if (!navigation_controller) // We might not have a controller on init. + return NONE; + + NavigationEntry* entry = navigation_controller->GetActiveEntry(); + if (!entry) + return NONE; + + const NavigationEntry::SSLStatus& ssl = entry->ssl(); + switch (ssl.security_style()) { + case SECURITY_STYLE_UNKNOWN: + case SECURITY_STYLE_UNAUTHENTICATED: + return NONE; + + case SECURITY_STYLE_AUTHENTICATION_BROKEN: + return SECURITY_ERROR; + + case SECURITY_STYLE_AUTHENTICATED: + if (ssl.displayed_insecure_content()) + return SECURITY_WARNING; + if (net::IsCertStatusError(ssl.cert_status())) { + DCHECK_EQ(ssl.cert_status() & net::CERT_STATUS_ALL_ERRORS, + net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION); + return SECURITY_WARNING; + } + if ((ssl.cert_status() & net::CERT_STATUS_IS_EV) && + CertStore::GetInstance()->RetrieveCert(ssl.cert_id(), NULL)) + return EV_SECURE; + return SECURE; + + default: + NOTREACHED(); + return NONE; + } +} + +int ToolbarModel::GetIcon() const { + static int icon_ids[NUM_SECURITY_LEVELS] = { + IDR_OMNIBOX_HTTP, + IDR_OMNIBOX_HTTPS_VALID, + IDR_OMNIBOX_HTTPS_VALID, + IDR_OMNIBOX_HTTPS_WARNING, + IDR_OMNIBOX_HTTPS_INVALID, + }; + DCHECK(arraysize(icon_ids) == NUM_SECURITY_LEVELS); + return icon_ids[GetSecurityLevel()]; +} + +std::wstring ToolbarModel::GetEVCertName() const { + DCHECK_EQ(GetSecurityLevel(), EV_SECURE); + scoped_refptr<net::X509Certificate> cert; + // Note: Navigation controller and active entry are guaranteed non-NULL or + // the security level would be NONE. + CertStore::GetInstance()->RetrieveCert( + GetNavigationController()->GetActiveEntry()->ssl().cert_id(), &cert); + return SSLManager::GetEVCertName(*cert); +} + +NavigationController* ToolbarModel::GetNavigationController() const { + // This |current_tab| can be NULL during the initialization of the + // toolbar during window creation (i.e. before any tabs have been added + // to the window). + TabContents* current_tab = browser_->GetSelectedTabContents(); + return current_tab ? ¤t_tab->controller() : NULL; +} diff --git a/chrome/browser/ui/toolbar/toolbar_model.h b/chrome/browser/ui/toolbar/toolbar_model.h new file mode 100644 index 0000000..d9ebec5 --- /dev/null +++ b/chrome/browser/ui/toolbar/toolbar_model.h @@ -0,0 +1,71 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" + +class Browser; +class NavigationController; + +// This class is the model used by the toolbar, location bar and autocomplete +// edit. It populates its states from the current navigation entry retrieved +// from the navigation controller returned by GetNavigationController(). +class ToolbarModel { + public: + // TODO(wtc): unify ToolbarModel::SecurityLevel with SecurityStyle. We + // don't need two sets of security UI levels. SECURITY_STYLE_AUTHENTICATED + // needs to be refined into three levels: warning, standard, and EV. + enum SecurityLevel { + NONE = 0, // HTTP/no URL/user is editing + EV_SECURE, // HTTPS with valid EV cert + SECURE, // HTTPS (non-EV) + SECURITY_WARNING, // HTTPS, but unable to check certificate revocation + // status or with insecure content on the page + SECURITY_ERROR, // Attempted HTTPS and failed, page not authenticated + NUM_SECURITY_LEVELS, + }; + + explicit ToolbarModel(Browser* browser); + ~ToolbarModel(); + + // Returns the text that should be displayed in the location bar. + std::wstring GetText() const; + + // Returns the security level that the toolbar should display. + SecurityLevel GetSecurityLevel() const; + + // Returns the resource_id of the icon to show to the left of the address, + // based on the current URL. This doesn't cover specialized icons while the + // user is editing; see AutocompleteEditView::GetIcon(). + int GetIcon() const; + + // Returns the name of the EV cert holder. Only call this when the security + // level is EV_SECURE. + std::wstring GetEVCertName() const; + + // Getter/setter of whether the text in location bar is currently being + // edited. + void set_input_in_progress(bool value) { input_in_progress_ = value; } + bool input_in_progress() const { return input_in_progress_; } + + private: + // Returns the navigation controller used to retrieve the navigation entry + // from which the states are retrieved. + // If this returns NULL, default values are used. + NavigationController* GetNavigationController() const; + + Browser* browser_; + + // Whether the text in the location bar is currently being edited. + bool input_in_progress_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ToolbarModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/wrench_menu_model.cc b/chrome/browser/ui/toolbar/wrench_menu_model.cc new file mode 100644 index 0000000..cd8a114 --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model.cc @@ -0,0 +1,536 @@ +// Copyright (c) 2010 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 "chrome/browser/ui/toolbar/wrench_menu_model.h" + +#include <algorithm> +#include <cmath> + +#include "app/l10n_util.h" +#include "app/menus/button_menu_item_model.h" +#include "app/resource_bundle.h" +#include "base/command_line.h" +#include "base/i18n/number_formatting.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/background_page_tracker.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_ui_util.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/toolbar/encoding_menu_controller.h" +#include "chrome/browser/upgrade_detector.h" +#include "chrome/common/badge_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +#if defined(OS_LINUX) +#include <gtk/gtk.h> +#include "chrome/browser/gtk/gtk_util.h" +#endif + +#if defined(OS_MACOSX) +#include "chrome/browser/ui/browser_window.h" +#endif + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros/update_library.h" +#endif + +#if defined(OS_WIN) +#include "chrome/browser/enumerate_modules_model_win.h" +#endif + +// The size of the font used for dynamic text overlays on menu items. +const float kMenuBadgeFontSize = 12.0; + +namespace { +SkBitmap GetBackgroundPageIcon() { + string16 pages = base::FormatNumber( + BackgroundPageTracker::GetInstance()->GetBackgroundPageCount()); + return badge_util::DrawBadgeIconOverlay( + *ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_BACKGROUND_MENU), + kMenuBadgeFontSize, + pages, + l10n_util::GetStringUTF16(IDS_BACKGROUND_PAGE_BADGE_OVERFLOW)); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// EncodingMenuModel + +EncodingMenuModel::EncodingMenuModel(Browser* browser) + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + browser_(browser) { + Build(); +} + +EncodingMenuModel::~EncodingMenuModel() { +} + +void EncodingMenuModel::Build() { + EncodingMenuController::EncodingMenuItemList encoding_menu_items; + EncodingMenuController encoding_menu_controller; + encoding_menu_controller.GetEncodingMenuItems(browser_->profile(), + &encoding_menu_items); + + int group_id = 0; + EncodingMenuController::EncodingMenuItemList::iterator it = + encoding_menu_items.begin(); + for (; it != encoding_menu_items.end(); ++it) { + int id = it->first; + string16& label = it->second; + if (id == 0) { + AddSeparator(); + } else { + if (id == IDC_ENCODING_AUTO_DETECT) { + AddCheckItem(id, label); + } else { + // Use the id of the first radio command as the id of the group. + if (group_id <= 0) + group_id = id; + AddRadioItem(id, label, group_id); + } + } + } +} + +bool EncodingMenuModel::IsCommandIdChecked(int command_id) const { + TabContents* current_tab = browser_->GetSelectedTabContents(); + if (!current_tab) + return false; + EncodingMenuController controller; + return controller.IsItemChecked(browser_->profile(), + current_tab->encoding(), command_id); +} + +bool EncodingMenuModel::IsCommandIdEnabled(int command_id) const { + bool enabled = browser_->command_updater()->IsCommandEnabled(command_id); + // Special handling for the contents of the Encoding submenu. On Mac OS, + // instead of enabling/disabling the top-level menu item, the submenu's + // contents get disabled, per Apple's HIG. +#if defined(OS_MACOSX) + enabled &= browser_->command_updater()->IsCommandEnabled(IDC_ENCODING_MENU); +#endif + return enabled; +} + +bool EncodingMenuModel::GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { + return false; +} + +void EncodingMenuModel::ExecuteCommand(int command_id) { + browser_->ExecuteCommand(command_id); +} + +//////////////////////////////////////////////////////////////////////////////// +// ZoomMenuModel + +ZoomMenuModel::ZoomMenuModel(menus::SimpleMenuModel::Delegate* delegate) + : SimpleMenuModel(delegate) { + Build(); +} + +ZoomMenuModel::~ZoomMenuModel() { +} + +void ZoomMenuModel::Build() { + AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS); + AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL); + AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS); +} + +//////////////////////////////////////////////////////////////////////////////// +// ToolsMenuModel + +ToolsMenuModel::ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, + Browser* browser) + : SimpleMenuModel(delegate) { + Build(browser); +} + +ToolsMenuModel::~ToolsMenuModel() {} + +void ToolsMenuModel::Build(Browser* browser) { + AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR); + + AddSeparator(); + +#if !defined(OS_CHROMEOS) +#if defined(OS_MACOSX) + AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_APPLICATION_MAC); +#else + AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS); +#endif + AddSeparator(); +#endif + + AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS); + AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); + AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA); + + AddSeparator(); +#if defined(OS_CHROMEOS) || defined(OS_WIN) || defined(OS_LINUX) + AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK); + AddSeparator(); +#endif + + encoding_menu_model_.reset(new EncodingMenuModel(browser)); + AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU, + encoding_menu_model_.get()); + AddItemWithStringId(IDC_VIEW_SOURCE, IDS_VIEW_SOURCE); + if (g_browser_process->have_inspector_files()) { + AddItemWithStringId(IDC_DEV_TOOLS, IDS_DEV_TOOLS); + AddItemWithStringId(IDC_DEV_TOOLS_CONSOLE, IDS_DEV_TOOLS_CONSOLE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// WrenchMenuModel + +WrenchMenuModel::WrenchMenuModel(menus::AcceleratorProvider* provider, + Browser* browser) + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + provider_(provider), + browser_(browser), + tabstrip_model_(browser_->tabstrip_model()) { + Build(); + UpdateZoomControls(); + + tabstrip_model_->AddObserver(this); + + registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(browser_->profile())); + registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, + NotificationService::AllSources()); +} + +WrenchMenuModel::~WrenchMenuModel() { + if (tabstrip_model_) + tabstrip_model_->RemoveObserver(this); +} + +bool WrenchMenuModel::DoesCommandIdDismissMenu(int command_id) const { + return command_id != IDC_ZOOM_MINUS && command_id != IDC_ZOOM_PLUS; +} + +bool WrenchMenuModel::IsItemForCommandIdDynamic(int command_id) const { + return command_id == IDC_ZOOM_PERCENT_DISPLAY || +#if defined(OS_MACOSX) + command_id == IDC_FULLSCREEN || +#endif + command_id == IDC_SYNC_BOOKMARKS || + command_id == IDC_VIEW_BACKGROUND_PAGES; +} + +bool WrenchMenuModel::GetIconForCommandId(int command_id, + SkBitmap* bitmap) const { + switch (command_id) { + case IDC_VIEW_BACKGROUND_PAGES: { + int num_pages = BackgroundPageTracker::GetInstance()-> + GetUnacknowledgedBackgroundPageCount(); + if (num_pages > 0) { + *bitmap = GetBackgroundPageIcon(); + return true; + } else { + // No icon. + return false; + } + } + default: + // No icon for other dynamic menu items. + return false; + } +} + +string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const { + switch (command_id) { + case IDC_SYNC_BOOKMARKS: + return GetSyncMenuLabel(); + case IDC_ZOOM_PERCENT_DISPLAY: + return zoom_label_; +#if defined(OS_MACOSX) + case IDC_FULLSCREEN: { + int string_id = IDS_ENTER_FULLSCREEN_MAC; // Default to Enter. + // Note: On startup, |window()| may be NULL. + if (browser_->window() && browser_->window()->IsFullscreen()) + string_id = IDS_EXIT_FULLSCREEN_MAC; + return l10n_util::GetStringUTF16(string_id); + } +#endif + case IDC_VIEW_BACKGROUND_PAGES: { + string16 num_background_pages = base::FormatNumber( + BackgroundPageTracker::GetInstance()->GetBackgroundPageCount()); + return l10n_util::GetStringFUTF16(IDS_VIEW_BACKGROUND_PAGES, + num_background_pages); + } + default: + NOTREACHED(); + return string16(); + } +} + +void WrenchMenuModel::ExecuteCommand(int command_id) { + browser_->ExecuteCommand(command_id); +} + +bool WrenchMenuModel::IsCommandIdChecked(int command_id) const { + if (command_id == IDC_SHOW_BOOKMARK_BAR) { + return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); + } + + return false; +} + +bool WrenchMenuModel::IsCommandIdEnabled(int command_id) const { +#if defined(OS_CHROMEOS) + // Special case because IDC_NEW_WINDOW item should be disabled in BWSI mode, + // but accelerator should work. + if (command_id == IDC_NEW_WINDOW && + CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) + return false; +#endif + + return browser_->command_updater()->IsCommandEnabled(command_id); +} + +bool WrenchMenuModel::IsCommandIdVisible(int command_id) const { + if (command_id == IDC_UPGRADE_DIALOG) { +#if defined(OS_CHROMEOS) + return (chromeos::CrosLibrary::Get()->GetUpdateLibrary()->status().status + == chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT); +#else + return UpgradeDetector::GetInstance()->notify_upgrade(); +#endif + } else if (command_id == IDC_VIEW_INCOMPATIBILITIES) { +#if defined(OS_WIN) + EnumerateModulesModel* loaded_modules = + EnumerateModulesModel::GetInstance(); + return loaded_modules->confirmed_bad_modules_detected() > 0; +#else + return false; +#endif + } else if (command_id == IDC_VIEW_BACKGROUND_PAGES) { + BackgroundPageTracker* tracker = BackgroundPageTracker::GetInstance(); + return tracker->GetBackgroundPageCount() > 0; + } + return true; +} + +bool WrenchMenuModel::GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { + return provider_->GetAcceleratorForCommandId(command_id, accelerator); +} + +void WrenchMenuModel::TabSelectedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index, + bool user_gesture) { + // The user has switched between tabs and the new tab may have a different + // zoom setting. + UpdateZoomControls(); +} + +void WrenchMenuModel::TabReplacedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index) { + UpdateZoomControls(); +} + +void WrenchMenuModel::TabStripModelDeleted() { + // During views shutdown, the tabstrip model/browser is deleted first, while + // it is the opposite in gtk land. + tabstrip_model_->RemoveObserver(this); + tabstrip_model_ = NULL; +} + +void WrenchMenuModel::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::ZOOM_LEVEL_CHANGED: + case NotificationType::NAV_ENTRY_COMMITTED: + UpdateZoomControls(); + break; + default: + NOTREACHED(); + } +} + +// For testing. +WrenchMenuModel::WrenchMenuModel() + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + provider_(NULL), + browser_(NULL), + tabstrip_model_(NULL) { +} + +void WrenchMenuModel::Build() { + AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB); + AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW); + AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, + IDS_NEW_INCOGNITO_WINDOW); + + AddSeparator(); +#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)) + // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the + // layout for this menu item in Toolbar.xib. It does, however, use the + // command_id value from AddButtonItem() to identify this special item. + edit_menu_item_model_.reset(new menus::ButtonMenuItemModel(IDS_EDIT, this)); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_CUT, IDS_CUT); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_COPY, IDS_COPY); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_PASTE, IDS_PASTE); + AddButtonItem(IDC_EDIT_MENU, edit_menu_item_model_.get()); +#else + // TODO(port): Move to the above. + CreateCutCopyPaste(); +#endif + + AddSeparator(); +#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)) + // WARNING: See above comment. + zoom_menu_item_model_.reset( + new menus::ButtonMenuItemModel(IDS_ZOOM_MENU, this)); + zoom_menu_item_model_->AddGroupItemWithStringId( + IDC_ZOOM_MINUS, IDS_ZOOM_MINUS2); + zoom_menu_item_model_->AddButtonLabel(IDC_ZOOM_PERCENT_DISPLAY, + IDS_ZOOM_PLUS2); + zoom_menu_item_model_->AddGroupItemWithStringId( + IDC_ZOOM_PLUS, IDS_ZOOM_PLUS2); + zoom_menu_item_model_->AddSpace(); + zoom_menu_item_model_->AddItemWithImage( + IDC_FULLSCREEN, IDR_FULLSCREEN_MENU_BUTTON); + AddButtonItem(IDC_ZOOM_MENU, zoom_menu_item_model_.get()); +#else + // TODO(port): Move to the above. + CreateZoomFullscreen(); +#endif + + AddSeparator(); + AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE); + AddItemWithStringId(IDC_FIND, IDS_FIND); + AddItemWithStringId(IDC_PRINT, IDS_PRINT); + + tools_menu_model_.reset(new ToolsMenuModel(this, browser_)); + AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_TOOLS_MENU, + tools_menu_model_.get()); + + AddSeparator(); +#if defined(ENABLE_REMOTING) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableRemoting)) { + AddItem(IDC_REMOTING_SETUP, + l10n_util::GetStringUTF16(IDS_REMOTING_SETUP_LABEL)); + } +#endif + AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER); + AddItemWithStringId(IDC_SHOW_HISTORY, IDS_SHOW_HISTORY); + AddItemWithStringId(IDC_SHOW_DOWNLOADS, IDS_SHOW_DOWNLOADS); + AddSeparator(); + +#if defined(OS_CHROMEOS) + AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS); +#elif defined(OS_MACOSX) + AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES); +#elif defined(OS_LINUX) + string16 preferences = gtk_util::GetStockPreferencesMenuLabel(); + if (!preferences.empty()) + AddItem(IDC_OPTIONS, preferences); + else + AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES); +#else + AddItemWithStringId(IDC_OPTIONS, IDS_OPTIONS); +#endif + +#if defined(OS_CHROMEOS) + const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME); +#else + const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); +#endif + // On Mac, there is no About item. + if (browser_defaults::kShowAboutMenuItem) { + AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16( + IDS_ABOUT, product_name)); + } + string16 num_background_pages = base::FormatNumber( + BackgroundPageTracker::GetInstance()->GetBackgroundPageCount()); + AddItem(IDC_VIEW_BACKGROUND_PAGES, l10n_util::GetStringFUTF16( + IDS_VIEW_BACKGROUND_PAGES, num_background_pages)); + AddItem(IDC_UPGRADE_DIALOG, l10n_util::GetStringFUTF16( + IDS_UPDATE_NOW, product_name)); + AddItem(IDC_VIEW_INCOMPATIBILITIES, l10n_util::GetStringUTF16( + IDS_VIEW_INCOMPATIBILITIES)); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SetIcon(GetIndexOfCommandId(IDC_UPGRADE_DIALOG), + *rb.GetBitmapNamed(IDR_UPDATE_MENU)); +#if defined(OS_WIN) + SetIcon(GetIndexOfCommandId(IDC_VIEW_INCOMPATIBILITIES), + *rb.GetBitmapNamed(IDR_CONFLICT_MENU)); +#endif + + AddItemWithStringId(IDC_HELP_PAGE, IDS_HELP_PAGE); + if (browser_defaults::kShowExitMenuItem) { + AddSeparator(); +#if defined(OS_CHROMEOS) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) { + AddItemWithStringId(IDC_EXIT, IDS_EXIT_GUEST_MODE); + } else { + AddItemWithStringId(IDC_EXIT, IDS_SIGN_OUT); + } +#else + AddItemWithStringId(IDC_EXIT, IDS_EXIT); +#endif + } +} + +void WrenchMenuModel::CreateCutCopyPaste() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. + AddItemWithStringId(IDC_CUT, IDS_CUT); + AddItemWithStringId(IDC_COPY, IDS_COPY); + AddItemWithStringId(IDC_PASTE, IDS_PASTE); +} + +void WrenchMenuModel::CreateZoomFullscreen() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. + AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS); + AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS); + AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN); +} + +void WrenchMenuModel::UpdateZoomControls() { + bool enable_increment = false; + bool enable_decrement = false; + int zoom_percent = 100; + if (browser_->GetSelectedTabContents()) { + zoom_percent = browser_->GetSelectedTabContents()->GetZoomPercent( + &enable_increment, &enable_decrement); + } + zoom_label_ = l10n_util::GetStringFUTF16( + IDS_ZOOM_PERCENT, base::IntToString16(zoom_percent)); +} + +string16 WrenchMenuModel::GetSyncMenuLabel() const { + return sync_ui_util::GetSyncMenuLabel( + browser_->profile()->GetOriginalProfile()->GetProfileSyncService()); +} diff --git a/chrome/browser/ui/toolbar/wrench_menu_model.h b/chrome/browser/ui/toolbar/wrench_menu_model.h new file mode 100644 index 0000000..2690ea9 --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model.h @@ -0,0 +1,150 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ +#pragma once + +#include "app/menus/accelerator.h" +#include "app/menus/button_menu_item_model.h" +#include "app/menus/simple_menu_model.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/tabs/tab_strip_model_observer.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class Browser; +class TabStripModel; + +namespace { +class MockWrenchMenuModel; +} // namespace + +// A menu model that builds the contents of an encoding menu. +class EncodingMenuModel : public menus::SimpleMenuModel, + public menus::SimpleMenuModel::Delegate { + public: + explicit EncodingMenuModel(Browser* browser); + virtual ~EncodingMenuModel(); + + // Overridden from menus::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const; + virtual bool IsCommandIdEnabled(int command_id) const; + virtual bool GetAcceleratorForCommandId(int command_id, + menus::Accelerator* accelerator); + virtual void ExecuteCommand(int command_id); + + private: + void Build(); + + Browser* browser_; // weak + + DISALLOW_COPY_AND_ASSIGN(EncodingMenuModel); +}; + +// A menu model that builds the contents of the zoom menu. +class ZoomMenuModel : public menus::SimpleMenuModel { + public: + explicit ZoomMenuModel(menus::SimpleMenuModel::Delegate* delegate); + virtual ~ZoomMenuModel(); + + private: + void Build(); + + DISALLOW_COPY_AND_ASSIGN(ZoomMenuModel); +}; + +class ToolsMenuModel : public menus::SimpleMenuModel { + public: + ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, Browser* browser); + virtual ~ToolsMenuModel(); + + private: + void Build(Browser* browser); + + scoped_ptr<EncodingMenuModel> encoding_menu_model_; + + DISALLOW_COPY_AND_ASSIGN(ToolsMenuModel); +}; + +// A menu model that builds the contents of the wrench menu. +class WrenchMenuModel : public menus::SimpleMenuModel, + public menus::SimpleMenuModel::Delegate, + public menus::ButtonMenuItemModel::Delegate, + public TabStripModelObserver, + public NotificationObserver { + public: + WrenchMenuModel(menus::AcceleratorProvider* provider, Browser* browser); + virtual ~WrenchMenuModel(); + + // Overridden for ButtonMenuItemModel::Delegate: + virtual bool DoesCommandIdDismissMenu(int command_id) const; + + // Overridden for both ButtonMenuItemModel::Delegate and SimpleMenuModel: + virtual bool IsItemForCommandIdDynamic(int command_id) const; + virtual string16 GetLabelForCommandId(int command_id) const; + virtual bool GetIconForCommandId(int command_id, SkBitmap* icon) const; + virtual void ExecuteCommand(int command_id); + virtual bool IsCommandIdChecked(int command_id) const; + virtual bool IsCommandIdEnabled(int command_id) const; + virtual bool IsCommandIdVisible(int command_id) const; + virtual bool GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator); + + // Overridden from TabStripModelObserver: + virtual void TabSelectedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index, + bool user_gesture); + virtual void TabReplacedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, int index); + virtual void TabStripModelDeleted(); + + // Overridden from NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Getters. + Browser* browser() const { return browser_; } + + // Calculates |zoom_label_| in response to a zoom change. + void UpdateZoomControls(); + + private: + // Testing constructor used for mocking. + friend class ::MockWrenchMenuModel; + WrenchMenuModel(); + + void Build(); + + // Adds custom items to the menu. Deprecated in favor of a cross platform + // model for button items. + void CreateCutCopyPaste(); + void CreateZoomFullscreen(); + + string16 GetSyncMenuLabel() const; + + // Models for the special menu items with buttons. + scoped_ptr<menus::ButtonMenuItemModel> edit_menu_item_model_; + scoped_ptr<menus::ButtonMenuItemModel> zoom_menu_item_model_; + + // Label of the zoom label in the zoom menu item. + string16 zoom_label_; + + // Tools menu. + scoped_ptr<ToolsMenuModel> tools_menu_model_; + + menus::AcceleratorProvider* provider_; // weak + + Browser* browser_; // weak + TabStripModel* tabstrip_model_; // weak + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(WrenchMenuModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc b/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc new file mode 100644 index 0000000..92a8125 --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2010 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 "chrome/browser/ui/toolbar/wrench_menu_model.h" + +#include "chrome/app/chrome_command_ids.h" +#include "chrome/test/browser_with_test_window_test.h" +#include "chrome/test/menu_model_test.h" +#include "chrome/test/testing_profile.h" +#include "grit/generated_resources.h" +#include "testing/gtest/include/gtest/gtest.h" + +class WrenchMenuModelTest : public BrowserWithTestWindowTest, + public menus::AcceleratorProvider { + public: + // Don't handle accelerators. + virtual bool GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { return false; } +}; + +// Copies parts of MenuModelTest::Delegate and combines them with the +// WrenchMenuModel since WrenchMenuModel is now a SimpleMenuModel::Delegate and +// not derived from SimpleMenuModel. +class TestWrenchMenuModel : public WrenchMenuModel { + public: + TestWrenchMenuModel(menus::AcceleratorProvider* provider, + Browser* browser) + : WrenchMenuModel(provider, browser), + execute_count_(0), + checked_count_(0), + enable_count_(0) { + } + + // Testing overrides to menus::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const { + bool val = WrenchMenuModel::IsCommandIdChecked(command_id); + if (val) + checked_count_++; + return val; + } + + virtual bool IsCommandIdEnabled(int command_id) const { + ++enable_count_; + return true; + } + + virtual void ExecuteCommand(int command_id) { ++execute_count_; } + + int execute_count_; + mutable int checked_count_; + mutable int enable_count_; +}; + +TEST_F(WrenchMenuModelTest, Basics) { + TestWrenchMenuModel model(this, browser()); + int itemCount = model.GetItemCount(); + + // Verify it has items. The number varies by platform, so we don't check + // the exact number. + EXPECT_GT(itemCount, 10); + + // Execute a couple of the items and make sure it gets back to our delegate. + // We can't use CountEnabledExecutable() here because the encoding menu's + // delegate is internal, it doesn't use the one we pass in. + model.ActivatedAt(0); + EXPECT_TRUE(model.IsEnabledAt(0)); + // Make sure to use the index that is not separator in all configurations. + model.ActivatedAt(2); + EXPECT_TRUE(model.IsEnabledAt(2)); + EXPECT_EQ(model.execute_count_, 2); + EXPECT_EQ(model.enable_count_, 2); + + model.execute_count_ = 0; + model.enable_count_ = 0; + + // Choose something from the tools submenu and make sure it makes it back to + // the delegate as well. Use the first submenu as the tools one. + int toolsModelIndex = -1; + for (int i = 0; i < itemCount; ++i) { + if (model.GetTypeAt(i) == menus::MenuModel::TYPE_SUBMENU) { + toolsModelIndex = i; + break; + } + } + EXPECT_GT(toolsModelIndex, -1); + menus::MenuModel* toolsModel = model.GetSubmenuModelAt(toolsModelIndex); + EXPECT_TRUE(toolsModel); + EXPECT_GT(toolsModel->GetItemCount(), 2); + toolsModel->ActivatedAt(2); + EXPECT_TRUE(toolsModel->IsEnabledAt(2)); + EXPECT_EQ(model.execute_count_, 1); + EXPECT_EQ(model.enable_count_, 1); +} + +class EncodingMenuModelTest : public BrowserWithTestWindowTest, + public MenuModelTest { +}; + +TEST_F(EncodingMenuModelTest, IsCommandIdCheckedWithNoTabs) { + EncodingMenuModel model(browser()); + ASSERT_EQ(NULL, browser()->GetSelectedTabContents()); + EXPECT_FALSE(model.IsCommandIdChecked(IDC_ENCODING_ISO88591)); +} |