diff options
Diffstat (limited to 'chrome/browser/views')
114 files changed, 30080 insertions, 0 deletions
diff --git a/chrome/browser/views/about_chrome_view.cc b/chrome/browser/views/about_chrome_view.cc new file mode 100644 index 0000000..157ccc6 --- /dev/null +++ b/chrome/browser/views/about_chrome_view.cc @@ -0,0 +1,490 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/about_chrome_view.h" + +#include <commdlg.h> + +#include "base/file_version_info.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/color_utils.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/restart_message_box.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/views/text_field.h" +#include "chrome/views/throbber.h" +#include "chrome/views/window.h" +#include "webkit/glue/webkit_glue.h" + +#include "generated_resources.h" + +//////////////////////////////////////////////////////////////////////////////// +// AboutChromeView, public: + +AboutChromeView::AboutChromeView(Profile* profile) + : dialog_(NULL), + profile_(profile), + about_dlg_background_(NULL), + about_title_label_(NULL), + version_label_(NULL), + main_text_label_(NULL), + check_button_status_(CHECKBUTTON_HIDDEN), + google_updater_(NULL) { + DCHECK(profile); + Init(); + + google_updater_ = new GoogleUpdate(); // This object deletes itself. + google_updater_->AddStatusChangeListener(this); +} + +AboutChromeView::~AboutChromeView() { + // The Google Updater will hold a pointer to us until it reports status, so we + // need to let it know that we will no longer be listening. + if (google_updater_) + google_updater_->RemoveStatusChangeListener(); +} + +void AboutChromeView::Init() { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + if (version_info.get() == NULL) { + NOTREACHED() << L"Failed to initialize about window"; + return; + } + + current_version_ = version_info->file_version(); + + std::wstring official; + if (version_info->is_official_build()) { + official = l10n_util::GetString(IDS_ABOUT_VERSION_OFFICIAL); + } else { + official = l10n_util::GetString(IDS_ABOUT_VERSION_UNOFFICIAL); + } + + // Views we will add to the *parent* of this dialog, since it will display + // next to the buttons which we don't draw ourselves. + throbber_.reset(new ChromeViews::Throbber(50, true)); + throbber_->SetParentOwned(false); + throbber_->SetVisible(false); + + SkBitmap* success_image = rb.GetBitmapNamed(IDR_UPDATE_UPTODATE); + success_indicator_.SetImage(*success_image); + success_indicator_.SetParentOwned(false); + + SkBitmap* update_available_image = rb.GetBitmapNamed(IDR_UPDATE_AVAILABLE); + update_available_indicator_.SetImage(*update_available_image); + update_available_indicator_.SetParentOwned(false); + + SkBitmap* timeout_image = rb.GetBitmapNamed(IDR_UPDATE_FAIL); + timeout_indicator_.SetImage(*timeout_image); + timeout_indicator_.SetParentOwned(false); + + update_label_.SetVisible(false); + update_label_.SetParentOwned(false); + + // Regular view controls we draw by ourself. First, we add the background + // image for the dialog. We have two different background bitmaps, one for + // LTR UIs and one for RTL UIs. We load the correct bitmap based on the UI + // layout of the view. + about_dlg_background_ = new ChromeViews::ImageView(); + SkBitmap* about_background; + if (UILayoutIsRightToLeft()) + about_background = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND_RTL); + else + about_background = rb.GetBitmapNamed(IDR_ABOUT_BACKGROUND); + + about_dlg_background_->SetImage(*about_background); + AddChildView(about_dlg_background_); + + // Add the dialog labels. + about_title_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_PRODUCT_NAME)); + about_title_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont).DeriveFont(18, BOLD_FONTTYPE)); + AddChildView(about_title_label_); + + // This is a text field so people can copy the version number from the dialog. + version_label_ = new ChromeViews::TextField(); + version_label_->SetText(current_version_); + version_label_->SetReadOnly(true); + version_label_->RemoveBorder(); + version_label_->SetFont(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont).DeriveFont(0, BOLD_FONTTYPE)); + version_label_->set_default_width_in_chars( + static_cast<int>(current_version_.size() + 1)); + AddChildView(version_label_); + + // Text to display at the bottom of the dialog. + std::wstring main_text = + l10n_util::GetString(IDS_ABOUT_VERSION_COMPANY_NAME) + L"\n" + + l10n_util::GetString(IDS_ABOUT_VERSION_COPYRIGHT) + L"\n" + + l10n_util::GetStringF(IDS_ABOUT_VERSION_LICENSE, + l10n_util::GetString(IDS_ABOUT_VERSION_LICENSE_URL)) + L"\n\n" + + official + L" " + version_info->last_change() + L"\n" + + UTF8ToWide(webkit_glue::GetDefaultUserAgent()); + + main_text_label_ = + new ChromeViews::TextField(ChromeViews::TextField::STYLE_MULTILINE); + main_text_label_->SetText(main_text); + main_text_label_->SetReadOnly(true); + main_text_label_->RemoveBorder(); + // Background color for the main label TextField. + SkColor main_label_background = color_utils::GetSysSkColor(COLOR_3DFACE); + main_text_label_->SetBackgroundColor(main_label_background); + AddChildView(main_text_label_); +} + +//////////////////////////////////////////////////////////////////////////////// +// AboutChromeView, ChromeViews::View implementation: + +void AboutChromeView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_ABOUT_DIALOG_WIDTH_CHARS, + IDS_ABOUT_DIALOG_HEIGHT_LINES).ToSIZE(); + // We compute the height of the dialog based on the size of the image (it + // would be nice to not hard code this), the text in the about dialog and the + // margins around the text. + out->cy += 145 + (kPanelVertMargin * 2); + // TODO(beng): Eventually the image should be positioned such that hard- + // coding the width isn't necessary. This breaks with fonts + // that are large and cause wrapping. + out->cx = 422; +} + +void AboutChromeView::Layout() { + CSize panel_size; + GetPreferredSize(&panel_size); + + CSize sz; + + // Background image for the dialog. + about_dlg_background_->GetPreferredSize(&sz); + int background_image_height = sz.cy; // used to position main text below. + about_dlg_background_->SetBounds(0, 0, sz.cx, sz.cy); + + // First label goes to the top left corner. + about_title_label_->GetPreferredSize(&sz); + about_title_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin, + sz.cx, sz.cy); + + // Then we have the version number right below it. + version_label_->GetPreferredSize(&sz); + version_label_->SetBounds(kPanelHorizMargin, + about_title_label_->GetY() + + about_title_label_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, + sz.cy); + + // For the width of the main text label we want to use up the whole panel + // width and remaining height, minus a little margin on each side. + int y_pos = background_image_height + kPanelVertMargin; + sz.cx = panel_size.cx - 2 * kPanelHorizMargin; + sz.cy = panel_size.cy - y_pos; + // Draw the text right below the background image. + main_text_label_->SetBounds(kPanelHorizMargin, + y_pos, + sz.cx, + sz.cy); + + // Get the y-coordinate of our parent so we can position the text left of the + // buttons at the bottom. + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + + throbber_->GetPreferredSize(&sz); + int throbber_topleft_x = kPanelHorizMargin; + int throbber_topleft_y = parent_bounds.bottom - sz.cy - + kButtonVEdgeMargin - 3; + throbber_->SetBounds(throbber_topleft_x, throbber_topleft_y, sz.cx, sz.cy); + + // This image is hidden (see ViewHierarchyChanged) and displayed on demand. + success_indicator_.GetPreferredSize(&sz); + success_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y, + sz.cx, sz.cy); + + // This image is hidden (see ViewHierarchyChanged) and displayed on demand. + update_available_indicator_.GetPreferredSize(&sz); + update_available_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y, + sz.cx, sz.cy); + + // This image is hidden (see ViewHierarchyChanged) and displayed on demand. + timeout_indicator_.GetPreferredSize(&sz); + timeout_indicator_.SetBounds(throbber_topleft_x, throbber_topleft_y, + sz.cx, sz.cy); + + // The update label should be at the bottom of the screen, to the right of + // the throbber. We specify width to the end of the dialog because it contains + // variable length messages. + update_label_.GetPreferredSize(&sz); + int update_label_x = throbber_->GetX() + throbber_->GetWidth() + + kRelatedControlHorizontalSpacing; + update_label_.SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + update_label_.SetBounds(update_label_x, + throbber_topleft_y + 1, + parent_bounds.Width() - update_label_x, + sz.cy); +} + +void AboutChromeView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + // Since we want the some of the controls to show up in the same visual row + // as the buttons, which are provided by the framework, we must add the + // buttons to the non-client view, which is the parent of this view. + // Similarly, when we're removed from the view hierarchy, we must take care + // to remove these items as well. + if (child == this) { + if (is_add) { + parent->AddChildView(&update_label_); + parent->AddChildView(throbber_.get()); + parent->AddChildView(&success_indicator_); + success_indicator_.SetVisible(false); + parent->AddChildView(&update_available_indicator_); + update_available_indicator_.SetVisible(false); + parent->AddChildView(&timeout_indicator_); + timeout_indicator_.SetVisible(false); + + // On-demand updates for Chrome don't work in Vista when UAC is turned + // off. So, in this case we just want the About box to not mention + // on-demand updates. Silent updates (in the background) should still + // work as before. + if (win_util::UserAccountControlIsEnabled()) { + UpdateStatus(UPGRADE_CHECK_STARTED, GOOGLE_UPDATE_NO_ERROR); + google_updater_->CheckForUpdate(false); // false=don't upgrade yet. + } + } else { + parent->RemoveChildView(&update_label_); + parent->RemoveChildView(throbber_.get()); + parent->RemoveChildView(&success_indicator_); + parent->RemoveChildView(&update_available_indicator_); + parent->RemoveChildView(&timeout_indicator_); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// AboutChromeView, ChromeViews::DialogDelegate implementation: + +int AboutChromeView::GetDialogButtons() const { + return DIALOGBUTTON_OK | DIALOGBUTTON_CANCEL; +} + +std::wstring AboutChromeView::GetDialogButtonLabel( + DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + return l10n_util::GetString(IDS_ABOUT_CHROME_UPDATE_CHECK); + } else if (button == DIALOGBUTTON_CANCEL) { + // The OK button (which is the default button) has been re-purposed to be + // 'Check for Updates' so we want the Cancel button should have the label + // OK but act like a Cancel button in all other ways. + return l10n_util::GetString(IDS_OK); + } + + NOTREACHED(); + return L""; +} + +bool AboutChromeView::IsDialogButtonEnabled(DialogButton button) const { + if (button == DIALOGBUTTON_OK && check_button_status_ != CHECKBUTTON_ENABLED) + return false; + + return true; +} + +bool AboutChromeView::IsDialogButtonVisible(DialogButton button) const { + if (button == DIALOGBUTTON_OK && check_button_status_ == CHECKBUTTON_HIDDEN) + return false; + + return true; +} + +bool AboutChromeView::CanResize() const { + return false; +} + +bool AboutChromeView::CanMaximize() const { + return false; +} + +bool AboutChromeView::IsAlwaysOnTop() const { + return false; +} + +bool AboutChromeView::HasAlwaysOnTopMenu() const { + return false; +} + +bool AboutChromeView::IsModal() const { + return true; +} + +std::wstring AboutChromeView::GetWindowTitle() const { + return l10n_util::GetString(IDS_ABOUT_CHROME_TITLE); +} + +bool AboutChromeView::Accept() { + UpdateStatus(UPGRADE_STARTED, GOOGLE_UPDATE_NO_ERROR); + + // The Upgrade button isn't available until we have received notification + // that an update is available, at which point this pointer should have been + // null-ed out. + DCHECK(!google_updater_); + google_updater_ = new GoogleUpdate(); // This object deletes itself. + google_updater_->AddStatusChangeListener(this); + google_updater_->CheckForUpdate(true); // true=upgrade if new version found. + + return false; // We never allow this button to close the window. +} + +//////////////////////////////////////////////////////////////////////////////// +// AboutChromeView, GoogleUpdateStatusListener implementation: + +void AboutChromeView::OnReportResults(GoogleUpdateUpgradeResult result, + GoogleUpdateErrorCode error_code, + const std::wstring& version) { + // The object will free itself after reporting its status, so we should + // no longer refer to it. + google_updater_ = NULL; + + // Make a note of which version Google Update is reporting is the latest + // version. + new_version_available_ = version; + if (!new_version_available_.empty()) { + new_version_available_ = std::wstring(L"(") + + new_version_available_ + + std::wstring(L")"); + } + UpdateStatus(result, error_code); +} + +//////////////////////////////////////////////////////////////////////////////// +// AboutChromeView, private: + +void AboutChromeView::UpdateStatus(GoogleUpdateUpgradeResult result, + GoogleUpdateErrorCode error_code) { + bool show_success_indicator = false; + bool show_update_available_indicator = false; + bool show_timeout_indicator = false; + bool show_throbber = false; + bool show_update_label = true; // Always visible, except at start. + + switch (result) { + case UPGRADE_STARTED: + UserMetrics::RecordAction(L"Upgrade_Started", profile_); + check_button_status_ = CHECKBUTTON_DISABLED; + show_throbber = true; + update_label_.SetText(l10n_util::GetString(IDS_UPGRADE_STARTED)); + break; + case UPGRADE_CHECK_STARTED: + UserMetrics::RecordAction(L"UpgradeCheck_Started", profile_); + check_button_status_ = CHECKBUTTON_HIDDEN; + show_throbber = true; + update_label_.SetText(l10n_util::GetString(IDS_UPGRADE_CHECK_STARTED)); + break; + case UPGRADE_IS_AVAILABLE: + UserMetrics::RecordAction(L"UpgradeCheck_UpgradeIsAvailable", profile_); + check_button_status_ = CHECKBUTTON_ENABLED; + update_label_.SetText( + l10n_util::GetStringF(IDS_UPGRADE_AVAILABLE, + l10n_util::GetString(IDS_PRODUCT_NAME))); + show_update_available_indicator = true; + break; + case UPGRADE_ALREADY_UP_TO_DATE: { + // Google Update reported that Chrome is up-to-date. Now make sure that we + // are running the latest version and if not, notify the user by falling + // into the next case of UPGRADE_SUCCESSFUL. + scoped_ptr<installer::Version> installed_version( + InstallUtil::GetChromeVersion(false)); + scoped_ptr<installer::Version> running_version( + installer::Version::GetVersionFromString(current_version_)); + if (!installed_version.get() || + !installed_version->IsHigherThan(running_version.get())) { + UserMetrics::RecordAction(L"UpgradeCheck_AlreadyUpToDate", profile_); + check_button_status_ = CHECKBUTTON_HIDDEN; + update_label_.SetText( + l10n_util::GetStringF(IDS_UPGRADE_ALREADY_UP_TO_DATE, + l10n_util::GetString(IDS_PRODUCT_NAME), + current_version_)); + show_success_indicator = true; + break; + } + // No break here as we want to notify user about upgrade if there is one. + } + case UPGRADE_SUCCESSFUL: + if (result == UPGRADE_ALREADY_UP_TO_DATE) + UserMetrics::RecordAction(L"UpgradeCheck_AlreadyUpgraded", profile_); + else + UserMetrics::RecordAction(L"UpgradeCheck_Upgraded", profile_); + check_button_status_ = CHECKBUTTON_HIDDEN; + update_label_.SetText(l10n_util::GetStringF(IDS_UPGRADE_SUCCESSFUL, + l10n_util::GetString(IDS_PRODUCT_NAME), + new_version_available_)); + show_success_indicator = true; + RestartMessageBox::ShowMessageBox(dialog_->GetHWND()); + break; + case UPGRADE_ERROR: + UserMetrics::RecordAction(L"UpgradeCheck_Error", profile_); + check_button_status_ = CHECKBUTTON_HIDDEN; + update_label_.SetText(l10n_util::GetStringF(IDS_UPGRADE_ERROR, + IntToWString(error_code))); + show_timeout_indicator = true; + break; + default: + NOTREACHED(); + } + + success_indicator_.SetVisible(show_success_indicator); + update_available_indicator_.SetVisible(show_update_available_indicator); + timeout_indicator_.SetVisible(show_timeout_indicator); + update_label_.SetVisible(show_update_label); + throbber_->SetVisible(show_throbber); + if (show_throbber) + throbber_->Start(); + else + throbber_->Stop(); + + // We have updated controls on the parent, so we need to update its layout. + View* parent = GetParent(); + parent->Layout(); + + // Check button may have appeared/disappeared. We cannot call this during + // ViewHierarchyChanged because the |dialog_| pointer hasn't been set yet. + if (dialog_) + dialog_->UpdateDialogButtons(); +} diff --git a/chrome/browser/views/about_chrome_view.h b/chrome/browser/views/about_chrome_view.h new file mode 100644 index 0000000..b304218 --- /dev/null +++ b/chrome/browser/views/about_chrome_view.h @@ -0,0 +1,135 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_ABOUT_CHROME_VIEW_H_ +#define CHROME_BROWSER_VIEWS_ABOUT_CHROME_VIEW_H_ + +#include "chrome/browser/google_update.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + class TextField; + class Throbber; + class Window; +} + +class Profile; + +//////////////////////////////////////////////////////////////////////////////// +// +// The AboutChromeView class is responsible for drawing the UI controls of the +// About Chrome dialog that allows the user to see what version is installed +// and check for updates. +// +//////////////////////////////////////////////////////////////////////////////// +class AboutChromeView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public GoogleUpdateStatusListener { + public: + explicit AboutChromeView(Profile* profile); + virtual ~AboutChromeView(); + + // Initialize the controls on the dialog. + void Init(); + + void SetDialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + // Overridden from ChromeViews::DialogDelegate: + virtual int GetDialogButtons() const; + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool IsDialogButtonEnabled(DialogButton button) const; + virtual bool IsDialogButtonVisible(DialogButton button) const; + virtual bool CanResize() const; + virtual bool CanMaximize() const; + virtual bool IsAlwaysOnTop() const; + virtual bool HasAlwaysOnTopMenu() const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + + // Overridden from GoogleUpdateStatusListener: + virtual void OnReportResults(GoogleUpdateUpgradeResult result, + GoogleUpdateErrorCode error_code, + const std::wstring& version); + + private: + // The visible state of the Check For Updates button. + enum CheckButtonStatus { + CHECKBUTTON_HIDDEN = 0, + CHECKBUTTON_DISABLED, + CHECKBUTTON_ENABLED, + }; + + // Update the UI to show the status of the upgrade. + void UpdateStatus(GoogleUpdateUpgradeResult result, + GoogleUpdateErrorCode error_code); + + ChromeViews::Window* dialog_; + + Profile* profile_; + + // UI elements on the dialog. + ChromeViews::ImageView* about_dlg_background_; + ChromeViews::Label* about_title_label_; + ChromeViews::TextField* version_label_; + ChromeViews::TextField* main_text_label_; + // UI elements we add to the parent view. + scoped_ptr<ChromeViews::Throbber> throbber_; + ChromeViews::ImageView success_indicator_; + ChromeViews::ImageView update_available_indicator_; + ChromeViews::ImageView timeout_indicator_; + ChromeViews::Label update_label_; + + // Keeps track of the visible state of the Check For Updates button. + CheckButtonStatus check_button_status_; + + // The class that communicates with Google Update to find out if an update is + // available and asks it to start an upgrade. + GoogleUpdate* google_updater_; + + // Our current version. + std::wstring current_version_; + + // The version Google Update reports is available to us. + std::wstring new_version_available_; + + DISALLOW_EVIL_CONSTRUCTORS(AboutChromeView); +}; + +#endif // CHROME_BROWSER_VIEWS_ABOUT_CHROME_VIEW_H_ diff --git a/chrome/browser/views/bookmark_bar_view.cc b/chrome/browser/views/bookmark_bar_view.cc new file mode 100644 index 0000000..9f505ed --- /dev/null +++ b/chrome/browser/views/bookmark_bar_view.cc @@ -0,0 +1,2204 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <limits> + +#include "chrome/browser/views/bookmark_bar_view.h" + +#include "base/base_drag_source.h" +#include "base/gfx/skia_utils.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/browser/download_util.h" +#include "chrome/browser/history/history_backend.h" +#include "chrome/browser/history/history_database.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/page_navigator.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/view_ids.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/event_utils.h" +#include "chrome/browser/views/input_window.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/favicon_size.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_types.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/chrome_menu.h" +#include "chrome/views/menu_button.h" +#include "chrome/views/tooltip_manager.h" +#include "generated_resources.h" + +using ChromeViews::BaseButton; +using ChromeViews::DropTargetEvent; +using ChromeViews::MenuButton; +using ChromeViews::MenuItemView; +using ChromeViews::View; + +// Margins around the content. +static const int kTopMargin = 2; +static const int kBottomMargin = 3; +static const int kLeftMargin = 1; +static const int kRightMargin = 0; + +// Preferred height of the bookmarks bar. +static const int kBarHeight = 29; + +// Preferred height of the bookmarks bar when only shown on the new tab page. +static const int kNewtabBarHeight = 57; + +// How inset the bookmarks bar is when displayed on the new tab page. This is +// in addition to the margins above. +static const int kNewtabHorizontalPadding = 8; +static const int kNewtabVerticalPadding = 12; + +// Padding between buttons. +static const int kButtonPadding = 0; + +// Command ids used in the menu allowing the user to choose when we're visible. +static const int kAlwaysShowCommandID = 1; + +// Whether the pref height has been calculated. +static bool calcedSize = false; + +// The preferred height is the height needed for one button. As it is a constant +// it is calculated once. +static int prefButtonHeight = 0; + +// Icon to display when one isn't found for the page. +static SkBitmap* kDefaultFavIcon = NULL; + +// Icon used for folders. +static SkBitmap* kFolderIcon = NULL; + +// Icon used for most other menu. +static SkBitmap* kBookmarkedOtherIcon = NULL; + +// Background color. +static const SkColor kBackgroundColor = SkColorSetRGB(237, 244, 252); + +// Border colors for the BookmarBarView. +static const SkColor kTopBorderColor = SkColorSetRGB(222, 234, 248); +static const SkColor kBottomBorderColor = SkColorSetRGB(178, 178, 178); + +// Background color for when the bookmarks bar is only being displayed on the +// new tab page - this color should match the background color of the new tab +// page (white, most likely). +static const SkColor kNewtabBackgroundColor = SkColorSetRGB(255, 255, 255); + +// Border color for the 'new tab' style bookmarks bar. +static const SkColor kNewtabBorderColor = SkColorSetRGB(195, 206, 224); + +// How round the 'new tab' style bookmarks bar is. +static const int kNewtabBarRoundness = 5; + +// Offset for where the menu is shown relative to the bottom of the +// BookmarkBarView. +static const int kMenuOffset = 3; + +// Delay during drag and drop before the menu pops up. This is only used if +// we can't get the value from the OS. +static const int kShowFolderDropMenuDelay = 400; + +// Color of the drop indicator. +static const SkColor kDropIndicatorColor = SK_ColorBLACK; + +// Width of the drop indicator. +static const int kDropIndicatorWidth = 2; + +// Distance between the bottom of the bar and the separator. +static const int kSeparatorMargin = 1; + +// Width of the separator between the recently bookmarked button and the +// overflow indicator. +static const int kSeparatorWidth = 4; + +// Starting x-coordinate of the separator line within a separator. +static const int kSeparatorStartX = 2; + +// Border color along the left edge of the view representing the most recently +// view pages. +static const SkColor kSeparatorColor = SkColorSetRGB(194, 205, 212); + +// Left-padding for the instructional text. +static const int kInstructionsPadding = 6; + +// Color of the instructional text. +static const SkColor kInstructionsColor = SkColorSetRGB(128, 128, 142); + +namespace { + +// Returns the tooltip text for the specified url and title. The returned +// text is clipped to fit within the bounds of the monitor. +// +// Note that we adjust the direction of both the URL and the title based on the +// locale so that pure LTR strings are displayed properly in RTL locales. +static std::wstring CreateToolTipForURLAndTitle(const gfx::Point& screen_loc, + const GURL& url, + const std::wstring& title, + const std::wstring& languages) { + const gfx::Rect monitor_bounds = win_util::GetMonitorBoundsForRect( + gfx::Rect(screen_loc.x(), screen_loc.y(), 1, 1)); + ChromeFont tt_font = ChromeViews::TooltipManager::GetDefaultFont(); + std::wstring result; + + // First the title. + if (!title.empty()) { + std::wstring localized_title; + if (l10n_util::AdjustStringForLocaleDirection(title, &localized_title)) + result.append(gfx::ElideText(localized_title, + tt_font, + monitor_bounds.width())); + else + result.append(gfx::ElideText(title, tt_font, monitor_bounds.width())); + } + + // Only show the URL if the url and title differ. + if (title != UTF8ToWide(url.spec())) { + if (!result.empty()) + result.append(ChromeViews::TooltipManager::GetLineSeparator()); + + // We need to explicitly specify the directionality of the URL's text to + // make sure it is treated as an LTR string when the context is RTL. For + // example, the URL "http://www.yahoo.com/" appears as + // "/http://www.yahoo.com" when rendered, as is, in an RTL context since + // the Unicode BiDi algorithm puts certain characters on the left by + // default. + std::wstring elided_url(gfx::ElideUrl(url, + tt_font, + monitor_bounds.width(), + languages)); + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + l10n_util::WrapStringWithLTRFormatting(&elided_url); + result.append(elided_url); + } + return result; +} + +// Returns the drag operations for the specified node. +static int GetDragOperationsForNode(BookmarkBarNode* node) { + if (node->GetType() == history::StarredEntry::URL) { + return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE | + DragDropTypes::DRAG_LINK; + } + return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; +} + +// BookmarkButton ------------------------------------------------------------- + +// Buttons used for the bookmarks on the bookmark bar. + +class BookmarkButton : public ChromeViews::TextButton { + public: + BookmarkButton(const GURL& url, + const std::wstring& title, + Profile* profile) + : TextButton(title), + url_(url), + profile_(profile) { + show_animation_.reset(new SlideAnimation(this)); + show_animation_->Show(); + } + + bool GetTooltipText(int x, int y, std::wstring* tooltip) { + CPoint location(x, y); + ConvertPointToScreen(this, &location); + *tooltip = CreateToolTipForURLAndTitle( + gfx::Point(location.x, location.y), url_, GetText(), + profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)); + return !tooltip->empty(); + } + + virtual bool IsTriggerableEvent(const ChromeViews::MouseEvent& e) { + return event_utils::IsPossibleDispositionEvent(e); + } + + virtual void Paint(ChromeCanvas *canvas) { + ChromeViews::TextButton::Paint(canvas); + + // Since we can't change the alpha of the button (it contains un-alphable + // text), we paint the bar background over the front of the button. As the + // bar background is a gradient, we have to paint the gradient at the + // size of the parent (hence all the margin math below). We can't use + // the parent's actual bounds because they differ from what is painted. + SkPaint paint; + paint.setAlpha(static_cast<int>( + (1.0 - show_animation_->GetCurrentValue()) * 255)); + paint.setShader(gfx::CreateGradientShader(0, + GetHeight() + kTopMargin + kBottomMargin, + kTopBorderColor, + kBackgroundColor))->safeUnref(); + canvas->FillRectInt(0, -kTopMargin, GetWidth(), + GetHeight() + kTopMargin + kBottomMargin, paint); + } + + virtual void AnimationProgressed(const Animation* animation) { + ChromeViews::TextButton::AnimationProgressed(animation); + SchedulePaint(); + } + + private: + const GURL& url_; + Profile* profile_; + scoped_ptr<SlideAnimation> show_animation_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkButton); +}; + +// DropInfo ------------------------------------------------------------------- + +// Tracks drops on the BookmarkBarView. + +struct DropInfo { + DropInfo() : drop_index(-1), is_menu_showing(false), valid(false) {} + + // Whether the data is valid. + bool valid; + + // Index into the model the drop is over. This is relative to the root node. + int drop_index; + + // If true, the menu is being shown. + bool is_menu_showing; + + // If true, the user is dropping on a node. This is only used for group + // nodes. + bool drop_on; + + // If true, the user is over the overflow button. + bool is_over_overflow; + + // If true, the user is over the other button. + bool is_over_other; + + // Coordinates of the drag (in terms of the BookmarkBarView). + int x; + int y; + + // The current drag operation. + int drag_operation; + + // DropData for the drop. + BookmarkDragData data; +}; + +// ModelChangedListener ------------------------------------------------------- + +// Interface implemented by controllers/views that need to be notified any +// time the model changes, typically to cancel an operation that is showing +// data from the model such as a menu. This isn't intended as a general +// way to be notified of changes, rather for cases where a controller/view is +// showing data from the model in a modal like setting and needs to cleanly +// exit the modal loop if the model changes out from under it. +// +// A controller/view that needs this notification should install itself as the +// ModelChangeListener via the SetModelChangedListener method when shown and +// reset the ModelChangeListener of the BookmarkBarView when it closes by way +// of either the SetModelChangedListener method or the +// ClearModelChangedListenerIfEquals method. +// +class ModelChangedListener { + public: + virtual ~ModelChangedListener() {} + // Invoked when the model changes. Should cancel the edit and close any + // dialogs. + virtual void ModelChanged() = 0; +}; + +// EditFolderController ------------------------------------------------------- + +// EditFolderController manages the editing and/or creation of a folder. If the +// user presses ok, the name change is committed to the database. +// +// EditFolderController deletes itself when the window is closed. +// +class EditFolderController : public InputWindowDelegate, + public ModelChangedListener { + public: + EditFolderController(BookmarkBarView* view, + BookmarkBarNode* node, + int visual_order, + bool is_new) + : view_(view), + node_(node), + visual_order_(visual_order), + is_new_(is_new) { + DCHECK(is_new_ || node); + window_ = CreateInputWindow(view->GetViewContainer()->GetHWND(), this); + view_->SetModelChangedListener(this); + } + + void Show() { + window_->Show(); + } + + virtual void ModelChanged() { + window_->Close(); + } + + private: + virtual std::wstring GetTextFieldLabel() { + return l10n_util::GetString(IDS_BOOMARK_BAR_EDIT_FOLDER_LABEL); + } + + virtual std::wstring GetTextFieldContents() { + if (is_new_) + return l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME); + return node_->GetTitle(); + } + + virtual bool IsValid(const std::wstring& text) { + return !text.empty(); + } + + virtual void InputAccepted(const std::wstring& text) { + view_->ClearModelChangedListenerIfEquals(this); + BookmarkBarModel* model = view_->GetProfile()->GetBookmarkBarModel(); + if (is_new_) + model->AddGroup(node_, visual_order_, text); + else + model->SetTitle(node_, text); + } + + virtual void InputCanceled() { + view_->ClearModelChangedListenerIfEquals(this); + } + + virtual void WindowClosing() { + view_->ClearModelChangedListenerIfEquals(this); + delete this; + } + + virtual std::wstring GetWindowTitle() const { + return is_new_ ? + l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE_NEW) : + l10n_util::GetString(IDS_BOOMARK_FOLDER_EDITOR_WINDOW_TITLE); + } + + BookmarkBarView* view_; + + // If is_new is true, this is the parent to create the new node under. + // Otherwise this is the node to change the title of. + BookmarkBarNode* node_; + + int visual_order_; + bool is_new_; + ChromeViews::Window* window_; + + DISALLOW_EVIL_CONSTRUCTORS(EditFolderController); +}; + +// BookmarkNodeMenuController ------------------------------------------------- + +// IDs for the menus we create. + +static const int kOpenBookmarkID = 2; +static const int kOpenBookmarkInNewWindowID = 3; +static const int kOpenBookmarkInNewTabID = 4; +static const int kOpenAllBookmarksID = 5; +static const int kOpenAllBookmarksInNewWindowID = 6; +static const int kEditBookmarkID = 7; +static const int kDeleteBookmarkID = 8; +static const int kAddBookmarkID = 9; +static const int kNewFolderID = 10; + +// BookmarkNodeMenuController manages the context menus shown for the bookmark +// bar and buttons on the bookmark bar. + +class BookmarkNodeMenuController : public ChromeViews::MenuDelegate, + public ModelChangedListener { + public: + BookmarkNodeMenuController(BookmarkBarView* view, + BookmarkBarNode* node) + : view_(view), + node_(node), + menu_(this) { + if (node->GetType() == history::StarredEntry::URL) { + menu_.AppendMenuItemWithLabel(kOpenBookmarkID, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN)); + menu_.AppendMenuItemWithLabel(kOpenBookmarkInNewTabID, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); + menu_.AppendMenuItemWithLabel(kOpenBookmarkInNewWindowID, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); + } else { + menu_.AppendMenuItemWithLabel(kOpenAllBookmarksID, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL)); + menu_.AppendMenuItemWithLabel(kOpenAllBookmarksInNewWindowID, + l10n_util::GetString(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); + } + menu_.AppendSeparator(); + + if (node->GetParent() != + view->GetProfile()->GetBookmarkBarModel()->root_node()) { + menu_.AppendMenuItemWithLabel(kEditBookmarkID, + l10n_util::GetString(IDS_BOOKMARK_BAR_EDIT)); + menu_.AppendMenuItemWithLabel(kDeleteBookmarkID, + l10n_util::GetString(IDS_BOOKMARK_BAR_REMOVE)); + } + + menu_.AppendMenuItemWithLabel(kAddBookmarkID, + l10n_util::GetString(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); + menu_.AppendMenuItemWithLabel(kNewFolderID, + l10n_util::GetString(IDS_BOOMARK_BAR_NEW_FOLDER)); + menu_.AppendSeparator(); + menu_.AppendMenuItem(kAlwaysShowCommandID, + l10n_util::GetString(IDS_BOOMARK_BAR_ALWAYS_SHOW), + MenuItemView::CHECKBOX); + } + + void RunMenuAt(int x, int y) { + // Record the current ModelChangedListener. It will be non-null when we're + // used as the context menu for another menu. + ModelChangedListener* last_listener = view_->GetModelChangedListener(); + + view_->SetModelChangedListener(this); + + // width/height don't matter here. + menu_.RunMenuAt(view_->GetViewContainer()->GetHWND(), gfx::Rect(x, y, 0, 0), + MenuItemView::TOPLEFT, false); + + if (view_->GetModelChangedListener() == this) + view_->SetModelChangedListener(last_listener); + } + + virtual void ModelChanged() { + menu_.Cancel(); + } + + private: + // Menu::Delegate method. Does the appropriate operation based on chosen + // menu item. + virtual void ExecuteCommand(int id) { + Profile* profile = view_->GetProfile(); + + switch (id) { + case kOpenBookmarkID: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Open", profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), CURRENT_TAB, + PageTransition::AUTO_BOOKMARK); + break; + + case kOpenBookmarkInNewWindowID: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewWindow", + profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_WINDOW, + PageTransition::AUTO_BOOKMARK); + break; + + case kOpenBookmarkInNewTabID: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenInNewTab", + profile); + + view_->GetPageNavigator()->OpenURL(node_->GetURL(), NEW_FOREGROUND_TAB, + PageTransition::AUTO_BOOKMARK); + break; + + case kOpenAllBookmarksID: + case kOpenAllBookmarksInNewWindowID: { + if (id == kOpenAllBookmarksID) { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_OpenAll", + profile); + } else { + UserMetrics::RecordAction( + L"BookmarkBar_ContextMenu_OpenAllInNewWindow", profile); + } + + BookmarkBarNode* node = node_; + PageNavigator* navigator = view_->GetPageNavigator(); + bool opened_url = false; + OpenAll(node, (id == kOpenAllBookmarksInNewWindowID), &navigator, + &opened_url); + break; + } + + case kEditBookmarkID: + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Edit", profile); + + if (node_->GetType() == history::StarredEntry::URL) { + BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), + view_->GetProfile(), node_->GetURL(), node_->GetTitle()); + } else { + // Controller deletes itself when done. + EditFolderController* controller = new EditFolderController( + view_, node_, -1, false); + controller->Show(); + } + break; + + case kDeleteBookmarkID: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Remove", profile); + + view_->model_->Remove(node_->GetParent(), + node_->GetParent()->IndexOfChild(node_)); + break; + } + + case kAddBookmarkID: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_Add", profile); + + BookmarkEditorView::Show(view_->GetViewContainer()->GetHWND(), + view_->GetProfile(), GURL(), std::wstring()); + break; + } + + case kNewFolderID: { + UserMetrics::RecordAction(L"BookmarkBar_ContextMenu_NewFolder", + profile); + + int visual_order; + BookmarkBarNode* parent = + GetParentAndVisualOrderForNewNode(&visual_order); + GetParentAndVisualOrderForNewNode(&visual_order); + // Controller deletes itself when done. + EditFolderController* controller = + new EditFolderController(view_, parent, visual_order, true); + controller->Show(); + break; + } + + case kAlwaysShowCommandID: + view_->ToggleWhenVisible(); + break; + + default: + NOTREACHED(); + } + } + + bool IsItemChecked(int id) const { + DCHECK(id == kAlwaysShowCommandID); + return view_->GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); + } + + // Opens a tab/window for node and recursively opens all descendants. + // If open_first_in_new_window is true, the first opened node is opened + // in a new window. navigator indicates the PageNavigator to use for + // new tabs. It is reset if open_first_in_new_window is true. + // opened_url is set to true the first time a new tab is opened. + void OpenAll(BookmarkBarNode* node, + bool open_first_in_new_window, + PageNavigator** navigator, + bool* opened_url) { + if (node->GetType() == history::StarredEntry::URL) { + WindowOpenDisposition disposition; + if (*opened_url) + disposition = NEW_BACKGROUND_TAB; + else if (open_first_in_new_window) + disposition = NEW_WINDOW; + else // Open in current window. + disposition = CURRENT_TAB; + (*navigator)->OpenURL(node->GetURL(), disposition, + PageTransition::AUTO_BOOKMARK); + if (!*opened_url) { + *opened_url = true; + if (open_first_in_new_window || disposition == CURRENT_TAB) { + // We opened the tab in a new window or in the current tab which + // likely reset the navigator. Need to reset the page navigator + // appropriately. + Browser* new_browser = BrowserList::GetLastActive(); + TabContents* current_tab = new_browser->GetSelectedTabContents(); + DCHECK(new_browser && current_tab); + if (new_browser && current_tab) + *navigator = current_tab; + } + } + } else { + // Group, recurse through children. + for (int i = 0; i < node->GetChildCount(); ++i) { + OpenAll(node->GetChild(i), open_first_in_new_window, navigator, + opened_url); + } + } + } + + virtual bool IsCommandEnabled(int id) const { + if (id == kOpenAllBookmarksID || id == kOpenAllBookmarksInNewWindowID) + return NodeHasURLs(node_); + + return true; + } + + // Returns true if the specified node is of type URL, or has a descendant + // of type URL. + static bool NodeHasURLs(BookmarkBarNode* node) { + if (node->GetType() == history::StarredEntry::URL) + return true; + + for (int i = 0; i < node->GetChildCount(); ++i) { + if (NodeHasURLs(node->GetChild(i))) + return true; + } + return false; + } + + // Returns the parent node and visual_order to use when adding new + // bookmarks/folders. + BookmarkBarNode* GetParentAndVisualOrderForNewNode(int* visual_order) { + if (node_->GetType() != history::StarredEntry::URL) { + // Adding to a group always adds to the end. + *visual_order = node_->GetChildCount(); + return node_; + } else { + DCHECK(node_->GetParent()); + *visual_order = node_->GetParent()->IndexOfChild(node_) + 1; + return node_->GetParent(); + } + } + + MenuItemView menu_; + BookmarkBarView* view_; + BookmarkBarNode* node_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkNodeMenuController); +}; + +// MenuRunner ----------------------------------------------------------------- + +// MenuRunner manages creation and showing of a menu containing BookmarkNodes. +// MenuRunner is used to show the contents of bookmark folders on the +// bookmark bar, other folder, or overflow bookmarks. +// +class MenuRunner : public ChromeViews::MenuDelegate, + public ModelChangedListener { + public: + // start_child_index is the index of the first child in node to add to the + // menu. + MenuRunner(BookmarkBarView* view, + BookmarkBarNode* node, + int start_child_index) + : view_(view), + node_(node), + menu_(this) { + int next_menu_id = 1; + menu_id_to_node_map_[menu_.GetCommand()] = node; + BuildMenu(node, start_child_index, &menu_, &next_menu_id); + } + + // Returns the node the menu is being run for. + BookmarkBarNode* GetNode() { + return node_; + } + + void RunMenuAt(HWND hwnd, + const gfx::Rect& bounds, + MenuItemView::AnchorPosition position, + bool for_drop) { + view_->SetModelChangedListener(this); + if (for_drop) + menu_.RunMenuForDropAt(hwnd, bounds, position); + else + menu_.RunMenuAt(hwnd, bounds, position, false); + view_->ClearModelChangedListenerIfEquals(this); + } + + // Notification that the favicon has finished loading. Reset the icon + // of the menu item. + void FavIconLoaded(BookmarkBarNode* node) { + if (node_to_menu_id_map_.find(node) != + node_to_menu_id_map_.end()) { + menu_.SetIcon(node->GetFavIcon(), node_to_menu_id_map_[node]); + } + } + + virtual void ModelChanged() { + if (context_menu_.get()) + context_menu_->ModelChanged(); + menu_.Cancel(); + } + + private: + // Creates an entry in menu for each child node of parent starting at + // start_child_index, recursively invoking this for any star groups. + void BuildMenu(BookmarkBarNode* parent, + int start_child_index, + MenuItemView* menu, + int* next_menu_id) { + DCHECK(!parent->GetChildCount() || + start_child_index < parent->GetChildCount()); + for (int i = start_child_index; i < parent->GetChildCount(); ++i) { + BookmarkBarNode* node = parent->GetChild(i); + int id = *next_menu_id; + + (*next_menu_id)++; + if (node->GetType() == history::StarredEntry::URL) { + SkBitmap icon = node->GetFavIcon(); + if (icon.width() == 0) + icon = *kDefaultFavIcon; + menu->AppendMenuItemWithIcon(id, node->GetTitle(), icon); + node_to_menu_id_map_[node] = id; + } else { + SkBitmap* folder_icon = + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_BOOKMARK_BAR_FOLDER); + MenuItemView* submenu = menu->AppendSubMenuWithIcon( + id, node->GetTitle(), *folder_icon); + BuildMenu(node, 0, submenu, next_menu_id); + } + menu_id_to_node_map_[id] = node; + } + } + + // ViewMenuDelegate method. Overridden to forward to the PageNavigator so + // that we accept any events that may trigger opening a url. + virtual bool IsTriggerableEvent(const ChromeViews::MouseEvent& e) { + return event_utils::IsPossibleDispositionEvent(e); + } + + // Invoked when a menu item is selected. Uses the PageNavigator set on + // the BookmarkBarView to open the URL. + virtual void ExecuteCommand(int id, int mouse_event_flags) { + DCHECK(view_->GetPageNavigator()); + GURL url; + DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); + url = menu_id_to_node_map_[id]->GetURL(); + view_->GetPageNavigator()->OpenURL( + url, event_utils::DispositionFromEventFlags(mouse_event_flags), + PageTransition::AUTO_BOOKMARK); + } + + virtual bool CanDrop(MenuItemView* menu, const OSExchangeData& data) { + if (!drop_data_.Read(data)) + return false; + + if (drop_data_.is_url) + return true; + + if (drop_data_.profile_id != view_->GetProfile()->GetID()) { + // Always accept drags of bookmark groups from other profiles. + return true; + } + // Drag originated from same profile and is not a URL. Only accept it if + // the dragged node is not a parent of the node menu represents. + BookmarkBarNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; + DCHECK(drop_node); + BookmarkBarNode* drag_node = drop_data_.GetNode(view_->GetProfile()-> + GetBookmarkBarModel()); + if (!drag_node) { + // Hmmm, can't find the dragged node. This is generally an error + // condition and we won't try and do anything fancy. + NOTREACHED(); + return false; + } + BookmarkBarNode* node = drop_node; + while (drop_node && drop_node != drag_node) + drop_node = drop_node->GetParent(); + return (drop_node == NULL); + } + + virtual int GetDropOperation(MenuItemView* item, + const ChromeViews::DropTargetEvent& event, + DropPosition* position) { + DCHECK(drop_data_.is_valid); + BookmarkBarNode* node = menu_id_to_node_map_[item->GetCommand()]; + BookmarkBarNode* drop_parent = node->GetParent(); + int index_to_drop_at = drop_parent->IndexOfChild(node); + if (*position == DROP_AFTER) { + index_to_drop_at++; + } else if (*position == DROP_ON) { + drop_parent = node; + index_to_drop_at = node->GetChildCount(); + } + DCHECK(drop_parent); + return view_->CalculateDropOperation(drop_data_, drop_parent, + index_to_drop_at); + } + + virtual int OnPerformDrop(MenuItemView* menu, + DropPosition position, + const DropTargetEvent& event) { + BookmarkBarNode* drop_node = menu_id_to_node_map_[menu->GetCommand()]; + DCHECK(drop_node); + BookmarkBarModel* model = view_->GetModel(); + DCHECK(model); + BookmarkBarNode* drop_parent = drop_node->GetParent(); + DCHECK(drop_parent); + int index_to_drop_at = drop_parent->IndexOfChild(drop_node); + if (position == DROP_AFTER) { + index_to_drop_at++; + } else if (position == DROP_ON) { + DCHECK(drop_node->GetType() != history::StarredEntry::URL); + drop_parent = drop_node; + index_to_drop_at = drop_node->GetChildCount(); + } + + const int result = view_->PerformDropImpl(drop_data_, drop_parent, + index_to_drop_at); + if (view_->drop_menu_runner_.get() == this) + view_->drop_menu_runner_.reset(); + // WARNING: we've been deleted! + return result; + } + + virtual void ShowContextMenu(MenuItemView* source, + int id, + int x, + int y, + bool is_mouse_gesture) { + DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); + context_menu_.reset( + new BookmarkNodeMenuController(view_, menu_id_to_node_map_[id])); + context_menu_->RunMenuAt(x, y); + context_menu_.reset(NULL); + } + + virtual void DropMenuClosed(MenuItemView* menu) { + if (view_->drop_menu_runner_.get() == this) + view_->drop_menu_runner_.reset(); + } + + virtual bool CanDrag(MenuItemView* menu) { + DCHECK(menu); + return true; + } + + virtual void WriteDragData(MenuItemView* sender, OSExchangeData* data) { + DCHECK(sender && data); + + UserMetrics::RecordAction(L"BookmarkBar_DragFromFolder", + view_->GetProfile()); + + view_->WriteDragData(menu_id_to_node_map_[sender->GetCommand()], data); + } + + virtual int GetDragOperations(MenuItemView* sender) { + return GetDragOperationsForNode( + menu_id_to_node_map_[sender->GetCommand()]); + } + + // The node we're showing the contents of. + BookmarkBarNode* node_; + + // The view that created us. + BookmarkBarView* view_; + + // The menu. + MenuItemView menu_; + + // Mapping from menu id to the BookmarkBarNode. + std::map<int, BookmarkBarNode*> menu_id_to_node_map_; + + // Mapping from node to menu id. This only contains entries for nodes of type + // URL. + std::map<BookmarkBarNode*, int> node_to_menu_id_map_; + + // Data for the drop. + BookmarkDragData drop_data_; + + scoped_ptr<BookmarkNodeMenuController> context_menu_; + + DISALLOW_EVIL_CONSTRUCTORS(MenuRunner); +}; + +// ButtonSeparatorView -------------------------------------------------------- + +// TODO(sky/glen): this is temporary, need to decide on what this should +// look like. +class ButtonSeparatorView : public ChromeViews::View { + public: + ButtonSeparatorView() {} + virtual ~ButtonSeparatorView() {} + + virtual void Paint(ChromeCanvas* canvas) { + SkPaint paint; + paint.setShader(gfx::CreateGradientShader(0, + GetHeight() / 2, + kTopBorderColor, + kSeparatorColor))->safeUnref(); + SkRect rc = {SkIntToScalar(kSeparatorStartX), SkIntToScalar(0), + SkIntToScalar(1), SkIntToScalar(GetHeight() / 2) }; + canvas->drawRect(rc, paint); + + SkPaint paint_down; + paint_down.setShader(gfx::CreateGradientShader(GetHeight() / 2, + GetHeight(), + kSeparatorColor, + kBackgroundColor))->safeUnref(); + SkRect rc_down = { + SkIntToScalar(kSeparatorStartX), SkIntToScalar(GetHeight() / 2), + SkIntToScalar(1), SkIntToScalar(GetHeight() - 1) }; + canvas->drawRect(rc_down, paint_down); + } + + virtual void GetPreferredSize(CSize* out) { + // We get the full height of the bookmark bar, so that the height returned + // here doesn't matter. + out->SetSize(kSeparatorWidth, 1); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ButtonSeparatorView); +}; + +} // namespace + +// BookmarkBarView ------------------------------------------------------------ + +// static +const int BookmarkBarView::kMaxButtonWidth = 150; + +// Returns the bitmap to use for starred groups. +static const SkBitmap& GetGroupIcon() { + if (!kFolderIcon) { + kFolderIcon = ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_BOOKMARK_BAR_FOLDER); + } + return *kFolderIcon; +} + +// static +void BookmarkBarView::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kShowBookmarkBar, false); +} + +BookmarkBarView::BookmarkBarView(Profile* profile, Browser* browser) + : profile_(NULL), + browser_(browser), + page_navigator_(NULL), + model_(NULL), + other_bookmarked_button_(NULL), + model_changed_listener_(NULL), + show_folder_drop_menu_task_(NULL), + overflow_button_(NULL), + instructions_(NULL), + bookmarks_separator_view_(NULL), + throbbing_view_(NULL) { + SetID(VIEW_ID_BOOKMARK_BAR); + Init(); + SetProfile(profile); + + + if (IsAlwaysShown()) { + size_animation_->Reset(1); + } else { + size_animation_->Reset(0); + } +} + +BookmarkBarView::~BookmarkBarView() { + NotifyModelChanged(); + RemoveNotificationObservers(); + if (model_) + model_->RemoveObserver(this); + StopShowFolderDropMenuTimer(); +} + +void BookmarkBarView::SetProfile(Profile* profile) { + DCHECK(profile); + if (profile_ == profile) + return; + + StopThrobbing(true); + + // Cancels the current cancelable. + NotifyModelChanged(); + + // Remove the current buttons. + for (int i = GetBookmarkButtonCount() - 1; i >= 0; --i) { + View* child = GetChildViewAt(i); + RemoveChildView(child); + delete child; + } + + profile_ = profile; + + if (model_) + model_->RemoveObserver(this); + + // Disable the other bookmarked button, we'll re-enable when the model is + // loaded. + other_bookmarked_button_->SetEnabled(false); + + NotificationService* ns = NotificationService::current(); + Source<Profile> ns_source(profile_->GetOriginalProfile()); + ns->AddObserver(this, NOTIFY_HISTORY_CREATED, ns_source); + ns->AddObserver(this, NOTIFY_BOOKMARK_BUBBLE_SHOWN, ns_source); + ns->AddObserver(this, NOTIFY_BOOKMARK_BUBBLE_HIDDEN, ns_source); + ns->AddObserver(this, NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, + NotificationService::AllSources()); + + if (!profile->HasHistoryService()) { + // The history service hasn't been loaded yet. We don't want to trigger + // loading it. Instead we install an observer that is notified when the + // history service has loaded. + model_ = NULL; + } else { + ProfileHasValidHistoryService(); + } +} + +void BookmarkBarView::SetPageNavigator(PageNavigator* navigator) { + page_navigator_ = navigator; +} + +void BookmarkBarView::GetPreferredSize(CSize *out) { + if (!prefButtonHeight) { + ChromeViews::TextButton text_button(L"X"); + CSize text_button_pref; + text_button.GetMinimumSize(&text_button_pref); + prefButtonHeight = static_cast<int>(text_button_pref.cy); + } + + if (IsNewTabPage()) { + out->cy = kBarHeight + static_cast<int>(static_cast<double> + (kNewtabBarHeight - kBarHeight) * + (1 - size_animation_->GetCurrentValue())); + } else { + out->cy = std::max(static_cast<int>(static_cast<double>(kBarHeight) * + size_animation_->GetCurrentValue()), 1); + } + + // Width doesn't matter, we're always given a width based on the browser size. + out->cx = 1; +} + +void BookmarkBarView::Layout() { + if (!GetParent()) + return; + + // First layout out the buttons. Any buttons that are placed beyond the + // visible region and made invisible. + int x = kLeftMargin; + int y = kTopMargin; + int width = GetWidth() - kRightMargin - kLeftMargin; + int height = GetHeight() - kTopMargin - kBottomMargin; + int separator_margin = kSeparatorMargin; + + if (IsNewTabPage()) { + double current_state = 1 - size_animation_->GetCurrentValue(); + x += static_cast<int>(static_cast<double> + (kNewtabHorizontalPadding) * current_state); + y += static_cast<int>(static_cast<double> + (kNewtabVerticalPadding) * current_state); + width -= static_cast<int>(static_cast<double> + (kNewtabHorizontalPadding) * current_state); + height -= static_cast<int>(static_cast<double> + (kNewtabVerticalPadding * 2) * current_state); + separator_margin -= static_cast<int>(static_cast<double> + (kSeparatorMargin) * current_state); + } + + CSize other_bookmarked_pref; + other_bookmarked_button_->GetPreferredSize(&other_bookmarked_pref); + CSize overflow_pref; + overflow_button_->GetPreferredSize(&overflow_pref); + CSize bookmarks_separator_pref; + bookmarks_separator_view_->GetPreferredSize(&bookmarks_separator_pref); + const int max_x = width - other_bookmarked_pref.cx - kButtonPadding - + overflow_pref.cx - kButtonPadding - + bookmarks_separator_pref.cx; + + if (GetBookmarkButtonCount() == 0 && model_ && model_->IsLoaded()) { + CSize pref; + instructions_->GetPreferredSize(&pref); + instructions_->SetBounds(x + kInstructionsPadding, y, + std::min(static_cast<int>(pref.cx), max_x - x), + height); + instructions_->SetVisible(true); + } else { + instructions_->SetVisible(false); + + for (int i = 0; i < GetBookmarkButtonCount(); ++i) { + ChromeViews::View* child = GetChildViewAt(i); + CSize pref; + child->GetPreferredSize(&pref); + int next_x = x + pref.cx + kButtonPadding; + child->SetVisible(next_x < max_x); + child->SetBounds(x, y, pref.cx, height); + x = next_x; + } + } + + // Layout the right side of the bar. + const bool all_visible = + (GetBookmarkButtonCount() == 0 || + GetChildViewAt(GetBookmarkButtonCount() - 1)->IsVisible()); + + // Layout the recently bookmarked button. + x = max_x + kButtonPadding; + + // The overflow button. + overflow_button_->SetBounds(x, y, overflow_pref.cx, height); + overflow_button_->SetVisible(!all_visible); + + x += overflow_pref.cx; + + // Separator. + bookmarks_separator_view_->SetBounds(x, + y - kTopMargin, + bookmarks_separator_pref.cx, + height + kTopMargin + kBottomMargin - + separator_margin); + x += bookmarks_separator_pref.cx; + + other_bookmarked_button_->SetBounds(x, y, other_bookmarked_pref.cx, height); + x += other_bookmarked_pref.cx + kButtonPadding; +} + +void BookmarkBarView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void BookmarkBarView::ViewHierarchyChanged(bool is_add, + View* parent, View* child) { + if (is_add && child == this && GetHeight() > 0) { + // We only layout while parented. When we become parented, if our bounds + // haven't changed, DidChangeBounds won't get invoked and we won't layout. + // Therefore we always force a layout when added. + Layout(); + } +} + +void BookmarkBarView::Paint(ChromeCanvas* canvas) { + int width = GetWidth(); + int height = GetHeight(); + + if (IsNewTabPage() && (!IsAlwaysShown() || size_animation_->IsAnimating())) { + // Draw the background to match the new tab page. + canvas->FillRectInt(kNewtabBackgroundColor, 0, 0, width, height); + + // Draw the 'bottom' of the toolbar above our bubble. + canvas->FillRectInt(kBottomBorderColor, 0, 0, width, 1); + + SkRect rect; + + // As 'hidden' according to the animation is the full in-tab state, + // we invert the value - when current_state is at '0', we expect the + // bar to be docked. + double current_state = 1 - size_animation_->GetCurrentValue(); + + // The 0.5 is to correct for Skia's "draw on pixel boundaries"ness. + double h_padding = static_cast<double> + (kNewtabHorizontalPadding) * current_state; + double v_padding = static_cast<double> + (kNewtabVerticalPadding) * current_state; + rect.set(SkDoubleToScalar(h_padding - 0.5), + SkDoubleToScalar(v_padding - 0.5), + SkDoubleToScalar(width - h_padding - 0.5), + SkDoubleToScalar(height - v_padding - 0.5)); + + double roundness = static_cast<double> + (kNewtabBarRoundness) * current_state; + + // Draw our background. + SkPaint paint; + paint.setAntiAlias(true); + paint.setShader(gfx::CreateGradientShader(0, + height, + kTopBorderColor, + kBackgroundColor))->safeUnref(); + + canvas->drawRoundRect(rect, + SkDoubleToScalar(roundness), + SkDoubleToScalar(roundness), paint); + + // Draw border + SkPaint border_paint; + border_paint.setColor(kNewtabBorderColor); + border_paint.setStyle(SkPaint::kStroke_Style); + border_paint.setAntiAlias(true); + + canvas->drawRoundRect(rect, + SkDoubleToScalar(roundness), + SkDoubleToScalar(roundness), border_paint); + } else { + SkPaint paint; + paint.setShader(gfx::CreateGradientShader(0, + height, + kTopBorderColor, + kBackgroundColor))->safeUnref(); + canvas->FillRectInt(0, 0, width, height, paint); + + canvas->FillRectInt(kTopBorderColor, 0, 0, width, 1); + canvas->FillRectInt(kBottomBorderColor, 0, height - 1, width, 1); + } +} + +void BookmarkBarView::PaintChildren(ChromeCanvas* canvas) { + View::PaintChildren(canvas); + + if (drop_info_.get() && drop_info_->valid && + drop_info_->drag_operation != 0 && drop_info_->drop_index != -1 && + !drop_info_->is_over_overflow && !drop_info_->drop_on) { + int index = drop_info_->drop_index; + DCHECK(index <= GetBookmarkButtonCount()); + int x = 0; + int y = 0; + int h = GetHeight(); + if (index == GetBookmarkButtonCount()) { + if (index == 0) { + x = kLeftMargin; + } else { + x = GetBookmarkButton(index - 1)->GetX() + + GetBookmarkButton(index - 1)->GetWidth(); + } + } else { + x = GetBookmarkButton(index)->GetX(); + } + if (GetBookmarkButtonCount() > 0 && GetBookmarkButton(0)->IsVisible()) { + y = GetBookmarkButton(0)->GetY(); + h = GetBookmarkButton(0)->GetHeight(); + } + + // Since the drop indicator is painted directly onto the canvas, we must + // make sure it is painted in the right location if the locale is RTL. + gfx::Rect indicator_bounds(x - kDropIndicatorWidth / 2, + y, + kDropIndicatorWidth, + h); + indicator_bounds.set_x(MirroredLeftPointForRect(indicator_bounds)); + + // TODO(sky/glen): make me pretty! + canvas->FillRectInt(kDropIndicatorColor, indicator_bounds.x(), + indicator_bounds.y(), indicator_bounds.width(), + indicator_bounds.height()); + } +} + +bool BookmarkBarView::CanDrop(const OSExchangeData& data) { + if (!model_ || !model_->IsLoaded()) + return false; + + if (!drop_info_.get()) + drop_info_.reset(new DropInfo()); + + return drop_info_->data.Read(data); +} + +void BookmarkBarView::OnDragEntered(const DropTargetEvent& event) { +} + +int BookmarkBarView::OnDragUpdated(const DropTargetEvent& event) { + if (!drop_info_.get()) + return 0; + + if (drop_info_->valid && + (drop_info_->x == event.GetX() && drop_info_->y == event.GetY())) { + return drop_info_->drag_operation; + } + + drop_info_->x = event.GetX(); + drop_info_->y = event.GetY(); + + int drop_index; + bool drop_on; + bool is_over_overflow; + bool is_over_other; + + drop_info_->drag_operation = CalculateDropOperation( + event, drop_info_->data, &drop_index, &drop_on, &is_over_overflow, + &is_over_other); + + if (drop_info_->valid && drop_info_->drop_index == drop_index && + drop_info_->drop_on == drop_on && + drop_info_->is_over_overflow == is_over_overflow && + drop_info_->is_over_other == is_over_other) { + return drop_info_->drag_operation; + } + + drop_info_->valid = true; + + StopShowFolderDropMenuTimer(); + + // TODO(sky): Optimize paint region. + SchedulePaint(); + drop_info_->drop_index = drop_index; + drop_info_->drop_on = drop_on; + drop_info_->is_over_overflow = is_over_overflow; + drop_info_->is_over_other = is_over_other; + + if (drop_info_->is_menu_showing) { + drop_menu_runner_.reset(); + drop_info_->is_menu_showing = false; + } + + if (drop_on || is_over_overflow || is_over_other) { + BookmarkBarNode* node; + if (is_over_other) + node = model_->other_node(); + else if (is_over_overflow) + node = model_->GetBookmarkBarNode(); + else + node = model_->GetBookmarkBarNode()->GetChild(drop_index); + StartShowFolderDropMenuTimer(node); + } + + return drop_info_->drag_operation; +} + +void BookmarkBarView::OnDragExited() { + StopShowFolderDropMenuTimer(); + + // NOTE: we don't hide the menu on exit as it's possible the user moved the + // mouse over the menu, which triggers an exit on us. + + drop_info_->valid = false; + + if (drop_info_->drop_index != -1) { + // TODO(sky): optimize the paint region. + SchedulePaint(); + } + drop_info_.reset(); +} + +int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) { + StopShowFolderDropMenuTimer(); + + drop_menu_runner_.reset(); + + if (!drop_info_.get() || !drop_info_->drag_operation) + return DragDropTypes::DRAG_NONE; + + BookmarkBarNode* root = + drop_info_->is_over_other ? model_->other_node() : + model_->GetBookmarkBarNode(); + int index = drop_info_->drop_index; + const bool drop_on = drop_info_->drop_on; + const BookmarkDragData data = drop_info_->data; + const bool is_over_other = drop_info_->is_over_other; + DCHECK(data.is_valid); + + if (drop_info_->drop_index != -1) { + // TODO(sky): optimize the SchedulePaint region. + SchedulePaint(); + } + drop_info_.reset(); + + BookmarkBarNode* parent_node; + if (is_over_other) { + parent_node = root; + index = parent_node->GetChildCount(); + } else if (drop_on) { + parent_node = root->GetChild(index); + index = parent_node->GetChildCount(); + } else { + parent_node = root; + } + return PerformDropImpl(data, parent_node, index); +} + +void BookmarkBarView::ToggleWhenVisible() { + PrefService* prefs = profile_->GetPrefs(); + const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar); + + // The user changed when the bookmark bar is shown, update the preferences. + prefs->SetBoolean(prefs::kShowBookmarkBar, always_show); + prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); + + // And notify the notification service. + Source<Profile> source(profile_); + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, + NotificationService::NoDetails()); + + // May need to redraw the bar with a new style. + SchedulePaint(); +} + +bool BookmarkBarView::IsAlwaysShown() { + return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +bool BookmarkBarView::IsNewTabPage() { + DCHECK(browser_); + + if (browser_->GetSelectedTabContents()) { + return browser_->GetSelectedTabContents()->IsBookmarkBarAlwaysVisible(); + } + + return false; +} + +void BookmarkBarView::AnimationProgressed(const Animation* animation) { + browser_->ToolbarSizeChanged(NULL, true); +} + +void BookmarkBarView::AnimationEnded(const Animation* animation) { + browser_->ToolbarSizeChanged(NULL, false); + SchedulePaint(); +} + +void BookmarkBarView::Init() { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + if (!calcedSize) + calcedSize = false; + + if (!kDefaultFavIcon) + kDefaultFavIcon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); + + other_bookmarked_button_ = CreateOtherBookmarkedButton(); + AddChildView(other_bookmarked_button_); + + overflow_button_ = CreateOverflowButton(); + AddChildView(overflow_button_); + + bookmarks_separator_view_ = new ButtonSeparatorView(); + AddChildView(bookmarks_separator_view_); + + instructions_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BOOKMARKS_NO_ITEMS), + rb.GetFont(ResourceBundle::BaseFont)); + instructions_->SetColor(kInstructionsColor); + AddChildView(instructions_); + + SetContextMenuController(this); + + size_animation_.reset(new SlideAnimation(this)); +} + +MenuButton* BookmarkBarView::CreateOtherBookmarkedButton() { + MenuButton* button = new MenuButton( + l10n_util::GetString(IDS_BOOMARK_BAR_OTHER_BOOKMARKED), this, false); + button->SetIcon(GetGroupIcon()); + button->SetContextMenuController(this); + return button; +} + +MenuButton* BookmarkBarView::CreateOverflowButton() { + MenuButton* button = new MenuButton(std::wstring(), this, false); + button->SetIcon(*ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_BOOKMARK_BAR_CHEVRONS)); + + // The overflow button's image contains an arrow and therefore it is a + // direction sensitive image and we need to flip it if the UI layout is + // right-to-left. + // + // By default, menu buttons are not flipped because they generally contain + // text and flipping the ChromeCanvas object will break text rendering. Since + // the overflow button does not contain text, we can safely flip it. + button->EnableCanvasFlippingForRTLUI(true); + + // Make visible as necessary. + button->SetVisible(false); + return button; +} + +int BookmarkBarView::GetBookmarkButtonCount() { + // We contain four non-bookmark button views: recently bookmarked, + // bookmarks separator, chevrons (for overflow) and the instruction + // label. + return GetChildViewCount() - 4; +} + +ChromeViews::TextButton* BookmarkBarView::GetBookmarkButton(int index) { + DCHECK(index >= 0 && index < GetBookmarkButtonCount()); + return static_cast<ChromeViews::TextButton*>(GetChildViewAt(index)); +} + +void BookmarkBarView::Loaded(BookmarkBarModel* model) { + BookmarkBarNode* node = model_->GetBookmarkBarNode(); + DCHECK(node && model_->other_node()); + // Create a button for each of the children on the bookmark bar. + for (int i = 0; i < node->GetChildCount(); ++i) + AddChildView(i, CreateBookmarkButton(node->GetChild(i))); + other_bookmarked_button_->SetEnabled(true); + Layout(); + SchedulePaint(); +} + +void BookmarkBarView::BookmarkNodeMoved(BookmarkBarModel* model, + BookmarkBarNode* old_parent, + int old_index, + BookmarkBarNode* new_parent, + int new_index) { + StopThrobbing(true); + BookmarkNodeRemovedImpl(model, old_parent, old_index); + BookmarkNodeAddedImpl(model, new_parent, new_index); + StartThrobbing(); +} + +void BookmarkBarView::BookmarkNodeAdded(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + StopThrobbing(true); + BookmarkNodeAddedImpl(model, parent, index); + StartThrobbing(); +} + +void BookmarkBarView::BookmarkNodeAddedImpl(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + NotifyModelChanged(); + if (parent != model_->GetBookmarkBarNode()) { + // We only care about nodes on the bookmark bar. + return; + } + DCHECK(index >= 0 && index <= GetBookmarkButtonCount()); + AddChildView(index, CreateBookmarkButton(parent->GetChild(index))); + Layout(); + SchedulePaint(); +} + +void BookmarkBarView::BookmarkNodeRemoved(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + StopThrobbing(true); + BookmarkNodeRemovedImpl(model, parent, index); + StartThrobbing(); +} + +void BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + NotifyModelChanged(); + if (parent != model_->GetBookmarkBarNode()) { + // We only care about nodes on the bookmark bar. + return; + } + DCHECK(index >= 0 && index < GetBookmarkButtonCount()); + ChromeViews::View* button = GetChildViewAt(index); + RemoveChildView(button); + MessageLoop::current()->DeleteSoon(FROM_HERE, button); + Layout(); + SchedulePaint(); +} + +void BookmarkBarView::BookmarkNodeChanged(BookmarkBarModel* model, + BookmarkBarNode* node) { + NotifyModelChanged(); + BookmarkNodeChangedImpl(model, node); +} + +void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkBarModel* model, + BookmarkBarNode* node) { + if (node->GetParent() != model_->GetBookmarkBarNode()) { + // We only care about nodes on the bookmark bar. + return; + } + int index = model_->GetBookmarkBarNode()->IndexOfChild(node); + DCHECK(index != -1); + ChromeViews::TextButton* button = GetBookmarkButton(index); + CSize old_pref; + button->GetPreferredSize(&old_pref); + ConfigureButton(node, button); + CSize new_pref; + button->GetPreferredSize(&new_pref); + if (old_pref.cx != new_pref.cx) { + Layout(); + SchedulePaint(); + } else if (button->IsVisible()) { + button->SchedulePaint(); + } +} + +void BookmarkBarView::BookmarkNodeFavIconLoaded(BookmarkBarModel* model, + BookmarkBarNode* node) { + if (menu_runner_.get()) + menu_runner_->FavIconLoaded(node); + if (drop_menu_runner_.get()) + drop_menu_runner_->FavIconLoaded(node); + BookmarkNodeChangedImpl(model, node); +} + +void BookmarkBarView::WriteDragData(View* sender, + int press_x, + int press_y, + OSExchangeData* data) { + UserMetrics::RecordAction(L"BookmarkBar_DragButton", profile_); + + for (int i = 0; i < GetBookmarkButtonCount(); ++i) { + if (sender == GetBookmarkButton(i)) { + ChromeViews::TextButton* button = GetBookmarkButton(i); + ChromeCanvas canvas(button->GetWidth(), button->GetHeight(), false); + button->Paint(&canvas, true); + drag_utils::SetDragImageOnDataObject(canvas, button->GetWidth(), + button->GetHeight(), press_x, + press_y, data); + WriteDragData(model_->GetBookmarkBarNode()->GetChild(i), data); + return; + } + } + NOTREACHED(); +} + +void BookmarkBarView::WriteDragData(BookmarkBarNode* node, + OSExchangeData* data) { + DCHECK(node && data); + BookmarkDragData drag_data(node); + drag_data.profile_id = GetProfile()->GetID(); + drag_data.Write(data); +} + +int BookmarkBarView::GetDragOperations(View* sender, int x, int y) { + for (int i = 0; i < GetBookmarkButtonCount(); ++i) { + if (sender == GetBookmarkButton(i)) { + return GetDragOperationsForNode( + model_->GetBookmarkBarNode()->GetChild(i)); + } + } + NOTREACHED(); + return DragDropTypes::DRAG_NONE; +} + +void BookmarkBarView::RunMenu(ChromeViews::View* view, + const CPoint& pt, + HWND hwnd) { + BookmarkBarNode* node; + MenuItemView::AnchorPosition anchor_point = MenuItemView::TOPLEFT; + + // When we set the menu's position, we must take into account the mirrored + // position of the View relative to its parent. This can be easily done by + // passing the right flag to View::GetX(). + int x = view->GetX(APPLY_MIRRORING_TRANSFORMATION); + int height = GetHeight() - kMenuOffset; + + if (IsNewTabPage() && !IsAlwaysShown()) + height -= kNewtabVerticalPadding; + + int start_index = 0; + if (view == other_bookmarked_button_) { + UserMetrics::RecordAction(L"BookmarkBar_ShowOtherBookmarks", profile_); + + node = model_->other_node(); + if (UILayoutIsRightToLeft()) + anchor_point = MenuItemView::TOPLEFT; + else + anchor_point = MenuItemView::TOPRIGHT; + } else if (view == overflow_button_) { + node = model_->GetBookmarkBarNode(); + start_index = GetFirstHiddenNodeIndex(); + if (UILayoutIsRightToLeft()) + anchor_point = MenuItemView::TOPLEFT; + else + anchor_point = MenuItemView::TOPRIGHT; + } else { + int button_index = GetChildIndex(view); + DCHECK(button_index != -1); + node = model_->GetBookmarkBarNode()->GetChild(button_index); + + // When the UI layout is RTL, the bookmarks are laid out from right to left + // and therefore when we display the menu we want it to be aligned with the + // bottom right corner of the bookmark item. + if (UILayoutIsRightToLeft()) + anchor_point = MenuItemView::TOPRIGHT; + else + anchor_point = MenuItemView::TOPLEFT; + } + CPoint screen_loc(x, 0); + View::ConvertPointToScreen(this, &screen_loc); + menu_runner_.reset(new MenuRunner(this, node, start_index)); + HWND parent_hwnd = reinterpret_cast<HWND>( + browser_->frame()->GetPlatformID()); + menu_runner_->RunMenuAt(parent_hwnd, + gfx::Rect(screen_loc.x, screen_loc.y, + view->GetWidth(), height), + anchor_point, + false); +} + +void BookmarkBarView::ButtonPressed(ChromeViews::BaseButton* sender) { + int index = GetChildIndex(sender); + DCHECK(index != -1); + BookmarkBarNode* node = model_->GetBookmarkBarNode()->GetChild(index); + DCHECK(page_navigator_); + page_navigator_->OpenURL( + node->GetURL(), + event_utils::DispositionFromEventFlags(sender->mouse_event_flags()), + PageTransition::AUTO_BOOKMARK); + UserMetrics::RecordAction(L"ClickedBookmarkBarURLButton", profile_); +} + +void BookmarkBarView::ShowContextMenu(View* source, + int x, + int y, + bool is_mouse_gesture) { + if (!model_->IsLoaded()) { + // Don't do anything if the model isn't loaded. + return; + } + + BookmarkBarNode* node = model_->GetBookmarkBarNode(); + if (source == other_bookmarked_button_) { + node = model_->other_node(); + } else if (source != this) { + // User clicked on one of the bookmark buttons, find which one they + // clicked on. + int bookmark_button_index = GetChildIndex(source); + DCHECK(bookmark_button_index != -1 && + bookmark_button_index < GetBookmarkButtonCount()); + node = model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); + } + BookmarkNodeMenuController controller(this, node); + controller.RunMenuAt(x, y); +} + +ChromeViews::View* BookmarkBarView::CreateBookmarkButton( + BookmarkBarNode* node) { + if (node->GetType() == history::StarredEntry::URL) { + BookmarkButton* button = new BookmarkButton(node->GetURL(), + node->GetTitle(), + GetProfile()); + button->SetListener(this, 0); + ConfigureButton(node, button); + return button; + } else { + ChromeViews::MenuButton* button = + new ChromeViews::MenuButton(node->GetTitle(), this, false); + button->SetIcon(GetGroupIcon()); + ConfigureButton(node, button); + return button; + } +} + +void BookmarkBarView::ConfigureButton(BookmarkBarNode* node, + ChromeViews::TextButton* button) { + button->SetText(node->GetTitle()); + button->ClearMaxTextSize(); + button->SetContextMenuController(this); + button->SetDragController(this); + if (node->GetType() == history::StarredEntry::URL) { + if (node->GetFavIcon().width() != 0) + button->SetIcon(node->GetFavIcon()); + else + button->SetIcon(*kDefaultFavIcon); + } + button->set_max_width(kMaxButtonWidth); +} + +bool BookmarkBarView::IsItemChecked(int id) const { + DCHECK(id == kAlwaysShowCommandID); + return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); +} + +void BookmarkBarView::ExecuteCommand(int id) { + ToggleWhenVisible(); +} + +void BookmarkBarView::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(profile_); + if (type == NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED) { + if (IsAlwaysShown()) { + size_animation_->Show(); + } else { + size_animation_->Hide(); + } + } else if (type == NOTIFY_HISTORY_CREATED) { + ProfileHasValidHistoryService(); + } else if (type == NOTIFY_BOOKMARK_BUBBLE_SHOWN) { + StopThrobbing(true); + bubble_url_ = *(Details<GURL>(details).ptr()); + StartThrobbing(); + } else if (type == NOTIFY_BOOKMARK_BUBBLE_HIDDEN) { + StopThrobbing(false); + bubble_url_ = GURL(); + } +} + +void BookmarkBarView::ProfileHasValidHistoryService() { + DCHECK(profile_); + model_ = profile_->GetBookmarkBarModel(); + DCHECK(model_); + model_->AddObserver(this); + if (model_->IsLoaded()) + Loaded(model_); +} + +void BookmarkBarView::RemoveNotificationObservers() { + NotificationService* ns = NotificationService::current(); + Source<Profile> ns_source(profile_->GetOriginalProfile()); + ns->RemoveObserver(this, NOTIFY_HISTORY_CREATED, ns_source); + ns->RemoveObserver(this, NOTIFY_BOOKMARK_BUBBLE_SHOWN, ns_source); + ns->RemoveObserver(this, NOTIFY_BOOKMARK_BUBBLE_HIDDEN, ns_source); + ns->RemoveObserver(this, NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, + NotificationService::AllSources()); +} + +void BookmarkBarView::NotifyModelChanged() { + if (model_changed_listener_) + model_changed_listener_->ModelChanged(); +} + +void BookmarkBarView::ShowDropFolderForNode(BookmarkBarNode* node) { + if (drop_menu_runner_.get() && drop_menu_runner_->GetNode() == node) { + // Already showing for the specified node. + return; + } + + int start_index = 0; + View* view_to_position_menu_from; + + // Note that both the anchor position and the position of the menu itself + // change depending on the locale. Also note that we must apply the + // mirroring transformation when querying for the child View bounds + // (View::GetX(), specifically) so that we end up with the correct screen + // coordinates if the View in question is mirrored. + MenuItemView::AnchorPosition anchor = MenuItemView::TOPLEFT; + if (node == model_->other_node()) { + view_to_position_menu_from = other_bookmarked_button_; + if (!UILayoutIsRightToLeft()) + anchor = MenuItemView::TOPRIGHT; + } else if (node == model_->GetBookmarkBarNode()) { + DCHECK(overflow_button_->IsVisible()); + view_to_position_menu_from = overflow_button_; + start_index = GetFirstHiddenNodeIndex(); + if (!UILayoutIsRightToLeft()) + anchor = MenuItemView::TOPRIGHT; + } else { + // Make sure node is still valid. + int index = -1; + for (int i = 0; i < GetBookmarkButtonCount(); ++i) { + if (model_->GetBookmarkBarNode()->GetChild(i) == node) { + index = i; + break; + } + } + if (index == -1) + return; + view_to_position_menu_from = GetBookmarkButton(index); + if (UILayoutIsRightToLeft()) + anchor = MenuItemView::TOPRIGHT; + } + + drop_info_->is_menu_showing = true; + drop_menu_runner_.reset(new MenuRunner(this, node, start_index)); + CPoint screen_loc(0, 0); + View::ConvertPointToScreen(view_to_position_menu_from, &screen_loc); + drop_menu_runner_->RunMenuAt( + GetViewContainer()->GetHWND(), + gfx::Rect(screen_loc.x, screen_loc.y, + view_to_position_menu_from->GetWidth(), + view_to_position_menu_from->GetHeight()), + anchor, true); +} + +void BookmarkBarView::StopShowFolderDropMenuTimer() { + if (show_folder_drop_menu_task_) + show_folder_drop_menu_task_->Cancel(); +} + +void BookmarkBarView::StartShowFolderDropMenuTimer(BookmarkBarNode* node) { + DCHECK(!show_folder_drop_menu_task_); + show_folder_drop_menu_task_ = new ShowFolderDropMenuTask(this, node); + static DWORD delay = 0; + if (!delay && !SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &delay, 0)) { + delay = kShowFolderDropMenuDelay; + } + MessageLoop::current()->PostDelayedTask(FROM_HERE, + show_folder_drop_menu_task_, delay); +} + +int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, + const BookmarkDragData& data, + int* index, + bool* drop_on, + bool* is_over_overflow, + bool* is_over_other) { + DCHECK(model_); + DCHECK(model_->IsLoaded()); + DCHECK(data.is_valid); + + // The drop event uses the screen coordinates while the child Views are + // always laid out from left to right (even though they are rendered from + // right-to-left on RTL locales). Thus, in order to make sure the drop + // coordinates calculation works, we mirror the event's X coordinate if the + // locale is RTL. + int mirrored_x = MirroredXCoordinateInsideView(event.GetX()); + + *index = -1; + *drop_on = false; + *is_over_other = *is_over_overflow = false; + + if (event.GetY() < other_bookmarked_button_->GetY() || + event.GetY() >= other_bookmarked_button_->GetY() + + other_bookmarked_button_->GetHeight()) { + // Mouse isn't over a button. + return DragDropTypes::DRAG_NONE; + } + + bool found = false; + const int other_delta_x = mirrored_x - other_bookmarked_button_->GetX(); + if (other_delta_x >= 0 && + other_delta_x < other_bookmarked_button_->GetWidth()) { + // Mouse is over 'other' folder. + *is_over_other = true; + *drop_on = true; + found = true; + } else if (!GetBookmarkButtonCount()) { + // No bookmarks, accept the drop. + *index = 0; + return DragDropTypes::DRAG_COPY; + } + + for (int i = 0; i < GetBookmarkButtonCount() && + GetBookmarkButton(i)->IsVisible() && !found; i++) { + ChromeViews::TextButton* button = GetBookmarkButton(i); + int button_x = mirrored_x - button->GetX(); + int button_w = button->GetWidth(); + if (button_x < button_w) { + found = true; + BookmarkBarNode* node = model_->GetBookmarkBarNode()->GetChild(i); + if (node->GetType() != history::StarredEntry::URL) { + if (button_x <= MenuItemView::kDropBetweenPixels) { + *index = i; + } else if (button_x < button_w - MenuItemView::kDropBetweenPixels) { + *index = i; + *drop_on = true; + } else { + *index = i + 1; + } + } else if (button_x < button_w / 2) { + *index = i; + } else { + *index = i + 1; + } + break; + } + } + + if (!found) { + if (overflow_button_->IsVisible()) { + // Are we over the overflow button? + int overflow_delta_x = mirrored_x - overflow_button_->GetX(); + if (overflow_delta_x >= 0 && + overflow_delta_x < overflow_button_->GetWidth()) { + // Mouse is over overflow button. + *index = GetFirstHiddenNodeIndex(); + *is_over_overflow = true; + } else if (overflow_delta_x < 0) { + // Mouse is after the last visible button but before overflow button; + // use the last visible index. + *index = GetFirstHiddenNodeIndex(); + } else { + return DragDropTypes::DRAG_NONE; + } + } else if (mirrored_x < other_bookmarked_button_->GetX()) { + // Mouse is after the last visible button but before more recently + // bookmarked; use the last visible index. + *index = GetFirstHiddenNodeIndex(); + } else { + return DragDropTypes::DRAG_NONE; + } + } + + if (*drop_on) { + BookmarkBarNode* parent = + *is_over_other ? model_->other_node() : + model_->GetBookmarkBarNode()->GetChild(*index); + int operation = + CalculateDropOperation(data, parent, parent->GetChildCount()); + if (!operation && !data.is_url && + data.profile_id == GetProfile()->GetID()) { + if (data.GetNode(model_) == parent) { + // Don't open a menu if the node being dragged is the the menu to + // open. + *drop_on = false; + } + } + return operation; + } else { + return CalculateDropOperation(data, model_->GetBookmarkBarNode(), *index); + } +} + +int BookmarkBarView::CalculateDropOperation(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index) { + if (!CanDropAt(data, parent, index)) + return DragDropTypes::DRAG_NONE; + + if (data.is_url) { + // User is dragging a URL. + BookmarkBarNode* node = model_->GetNodeByURL(data.url); + if (!node) { + // We don't have a node with this url. + return DragDropTypes::DRAG_COPY; + } + // Technically we're going to move, but most sources export as copy so that + // if we don't accept copy we won't accept the drop. + return DragDropTypes::DRAG_MOVE | DragDropTypes::DRAG_COPY; + } else if (data.profile_id == GetProfile()->GetID()) { + // Dropping a group from the same profile results in a move. + BookmarkBarNode* node = data.GetNode(model_); + if (!node) { + // Generally shouldn't get here, we originated the drag but couldn't + // find the node. + return DragDropTypes::DRAG_NONE; + } + return DragDropTypes::DRAG_MOVE; + } else { + // Dropping a group from different profile. Always accept. + return DragDropTypes::DRAG_COPY; + } +} + +bool BookmarkBarView::CanDropAt(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index) { + DCHECK(data.is_valid); + if (data.is_url) { + BookmarkBarNode* existing_node = model_->GetNodeByURL(data.url); + if (existing_node && existing_node->GetParent() == parent) { + const int existing_index = parent->IndexOfChild(existing_node); + if (index == existing_index || existing_index + 1 == index) + return false; + } + return true; + } else if (data.profile_id == profile_->GetID()) { + BookmarkBarNode* existing_node = data.GetNode(model_); + if (existing_node) { + if (existing_node->GetParent() == parent) { + const int existing_index = parent->IndexOfChild(existing_node); + if (index == existing_index || existing_index + 1 == index) + return false; + } + // Allow the drop only if the node we're going to drop on isn't a + // descendant of the dragged node. + BookmarkBarNode* test_node = parent; + while (test_node && test_node != existing_node) + test_node = test_node->GetParent(); + return (test_node == NULL); + } + } // else case clones, always allow. + return true; +} + + +int BookmarkBarView::PerformDropImpl(const BookmarkDragData& data, + BookmarkBarNode* parent_node, + int index) { + if (data.is_url) { + // User is dragging a URL. + BookmarkBarNode* node = model_->GetNodeByURL(data.url); + if (!node) { + std::wstring title = data.title; + if (title.empty()) { + // No title, use the host. + title = UTF8ToWide(data.url.host()); + if (title.empty()) + title = l10n_util::GetString(IDS_BOOMARK_BAR_UNKNOWN_DRAG_TITLE); + } + model_->AddURL(parent_node, index, title, data.url); + return DragDropTypes::DRAG_COPY; + } + model_->Move(node, parent_node, index); + return DragDropTypes::DRAG_MOVE; + } else if (data.profile_id == GetProfile()->GetID()) { + BookmarkBarNode* node = data.GetNode(model_); + if (!node) { + // Generally shouldn't get here, we originated the drag but couldn't + // find the node. Do nothing. + return DragDropTypes::DRAG_COPY; + } + model_->Move(node, parent_node, index); + return DragDropTypes::DRAG_MOVE; + } else { + // Dropping a group from different profile. Always accept. + CloneDragData(data, parent_node, index); + return DragDropTypes::DRAG_COPY; + } +} + +void BookmarkBarView::CloneDragData(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index_to_add_at) { + DCHECK(data.is_valid && model_); + if (data.is_url) { + BookmarkBarNode* node = model_->GetNodeByURL(data.url); + if (node) { + model_->Move(node, parent, index_to_add_at); + } else { + model_->AddURL(parent, index_to_add_at, data.title, data.url); + } + } else { + BookmarkBarNode* new_folder = model_->AddGroup(parent, index_to_add_at, + data.title); + for (int i = 0; i < static_cast<int>(data.children.size()); ++i) + CloneDragData(data.children[i], new_folder, i); + } +} + +int BookmarkBarView::GetFirstHiddenNodeIndex() { + const int bb_count = GetBookmarkButtonCount(); + for (int i = 0; i < bb_count; ++i) { + if (!GetBookmarkButton(i)->IsVisible()) + return i; + } + return bb_count; +} + +void BookmarkBarView::StartThrobbing() { + DCHECK(!throbbing_view_); + + if (bubble_url_.is_empty()) + return; // Bubble isn't showing; nothing to throb. + + if (!GetViewContainer()) + return; // We're not showing, don't do anything. + + BookmarkBarNode* node = model_->GetNodeByURL(bubble_url_); + if (!node) + return; // Generally shouldn't happen. + + // Determine which visible button is showing the url (or is an ancestor of + // the url). + if (node->HasAncestor(model_->GetBookmarkBarNode())) { + BookmarkBarNode* bbn = model_->GetBookmarkBarNode(); + BookmarkBarNode* parent_on_bb = node; + while (parent_on_bb->GetParent() != bbn) + parent_on_bb = parent_on_bb->GetParent(); + int index = bbn->IndexOfChild(parent_on_bb); + if (index >= GetFirstHiddenNodeIndex()) { + // Node is hidden, animate the overflow button. + throbbing_view_ = overflow_button_; + } else { + throbbing_view_ = static_cast<BaseButton*>(GetChildViewAt(index)); + } + } else { + throbbing_view_ = other_bookmarked_button_; + } + + // Use a large number so that the button continues to throb. + throbbing_view_->StartThrobbing(std::numeric_limits<int>::max()); +} + +void BookmarkBarView::StopThrobbing(bool immediate) { + if (!throbbing_view_) + return; + + // If not immediate, cycle through 2 more complete cycles. + throbbing_view_->StartThrobbing(immediate ? 0 : 4); + throbbing_view_ = NULL; +} diff --git a/chrome/browser/views/bookmark_bar_view.h b/chrome/browser/views/bookmark_bar_view.h new file mode 100644 index 0000000..b8ffa37 --- /dev/null +++ b/chrome/browser/views/bookmark_bar_view.h @@ -0,0 +1,424 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ + +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/bookmark_drag_data.h" +#include "chrome/common/slide_animation.h" +#include "chrome/views/label.h" +#include "chrome/views/menu.h" +#include "chrome/views/menu_button.h" +#include "chrome/views/view.h" +#include "chrome/views/view_menu_delegate.h" + +class PageNavigator; +class PrefService; +class Browser; + +namespace { +class BookmarkNodeMenuController; +// See description in declaration at bookmark_bar_view.cc. +class ModelChangedListener; +class MenuRunner; +class ButtonSeparatorView; +struct DropInfo; +} + +// BookmarkBarView renders the BookmarkBarModel. Each starred entry +// on the BookmarkBar is rendered as a MenuButton. An additional +// MenuButton aligned to the right allows the user to quickly see +// recently starred entries. +// +// BookmarkBarView shows the bookmarks from a specific Profile. BookmarkBarView +// waits until the HistoryService for the profile has been loaded before +// creating the BookmarkBarModel. +class BookmarkBarView : public ChromeViews::View, + public BookmarkBarModelObserver, + public ChromeViews::ViewMenuDelegate, + public ChromeViews::BaseButton::ButtonListener, + public Menu::Delegate, + public NotificationObserver, + public ChromeViews::ContextMenuController, + public ChromeViews::DragController, + public AnimationDelegate { + friend class BookmarkNodeMenuController; + friend class MenuRunner; + friend class ShowFolderMenuTask; + + public: + explicit BookmarkBarView(Profile* profile, Browser* browser); + virtual ~BookmarkBarView(); + + static void RegisterUserPrefs(PrefService* prefs); + + // Resets the profile. This removes any buttons for the current profile and + // recreates the models. + void SetProfile(Profile* profile); + + // Returns the current profile. + Profile* GetProfile() { return profile_; } + + // Sets the PageNavigator that is used when the user selects an entry on + // the bookmark bar. + void SetPageNavigator(PageNavigator* navigator); + + // View methods: + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual void Paint(ChromeCanvas* canvas); + virtual void PaintChildren(ChromeCanvas* canvas); + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const ChromeViews::DropTargetEvent& event); + virtual int OnDragUpdated(const ChromeViews::DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const ChromeViews::DropTargetEvent& event); + + // Sets the model change listener to listener. + void SetModelChangedListener(ModelChangedListener* listener) { + model_changed_listener_ = listener; + } + + // If the ModelChangedListener is listener, ModelChangeListener is set to + // NULL. + void ClearModelChangedListenerIfEquals(ModelChangedListener* listener) { + if (model_changed_listener_ == listener) + model_changed_listener_ = NULL; + } + + // Returns the model change listener. + ModelChangedListener* GetModelChangedListener() { + return model_changed_listener_; + } + + // Returns the page navigator. + PageNavigator* GetPageNavigator() { return page_navigator_; } + + // Returns the model. + BookmarkBarModel* GetModel() { return model_; } + + // Toggles whether the bookmark bar is shown only on the new tab page or on + // all tabs. + void ToggleWhenVisible(); + + // Returns true if the bookmarks bar preference is set to 'always show', we + // use this as a shorthand way of knowing what style of bar to draw (if the + // pref is set to false but we're painting, then we must be on the new tab + // page). + bool IsAlwaysShown(); + + // True if we're supposed to draw the bookmarks bar in the new tab style. + bool IsNewTabPage(); + + // SlideAnimationDelegate implementation. + void AnimationProgressed(const Animation* animation); + void AnimationEnded(const Animation* animation); + + // Maximum size of buttons on the bookmark bar. + static const int kMaxButtonWidth; + + private: + // Task that invokes ShowDropFolderForNode when run. ShowFolderDropMenuTask + // deletes itself once run. + class ShowFolderDropMenuTask : public Task { + public: + ShowFolderDropMenuTask(BookmarkBarView* view, + BookmarkBarNode* node) + : view_(view), + node_(node) { + } + + void Cancel() { + view_->show_folder_drop_menu_task_ = NULL; + view_ = NULL; + } + + virtual void Run() { + if (view_) { + view_->show_folder_drop_menu_task_ = NULL; + view_->ShowDropFolderForNode(node_); + } + // MessageLoop deletes us. + } + + private: + BookmarkBarView* view_; + BookmarkBarNode* node_; + + DISALLOW_EVIL_CONSTRUCTORS(ShowFolderDropMenuTask); + }; + + // Creates recent bookmark button and when visible button as well as + // calculating the preferred height. + void Init(); + + // Creates the button showing the other bookmarked items. + ChromeViews::MenuButton* CreateOtherBookmarkedButton(); + + // Creates the button used when not all bookmark buttons fit. + ChromeViews::MenuButton* CreateOverflowButton(); + + // Returns the number of buttons corresponding to starred urls/groups. This + // is equivalent to the number of children the bookmark bar node from the + // bookmark bar model has. + int GetBookmarkButtonCount(); + + // Returns the button at the specified index. + ChromeViews::TextButton* GetBookmarkButton(int index); + + // Invoked when the bookmark bar model has finished loading. Creates a button + // for each of the children of the root node from the model. + virtual void Loaded(BookmarkBarModel* model); + + // Invokes added followed by removed. + virtual void BookmarkNodeMoved(BookmarkBarModel* model, + BookmarkBarNode* old_parent, + int old_index, + BookmarkBarNode* new_parent, + int new_index); + + // Notifies ModelChangeListener of change. + // If the node was added to the root node, a button is created and added to + // this bookmark bar view. + virtual void BookmarkNodeAdded(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + + // Implementation for BookmarkNodeAddedImpl. + void BookmarkNodeAddedImpl(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + + // Notifies ModelChangeListener of change. + // If the node was a child of the root node, the button corresponding to it + // is removed. + virtual void BookmarkNodeRemoved(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + + // Implementation for BookmarkNodeRemoved. + void BookmarkNodeRemovedImpl(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + + // Notifies ModelChangedListener and invokes BookmarkNodeChangedImpl. + virtual void BookmarkNodeChanged(BookmarkBarModel* model, + BookmarkBarNode* node); + + // If the node is a child of the root node, the button is updated + // appropriately. + void BookmarkNodeChangedImpl(BookmarkBarModel* model, BookmarkBarNode* node); + + // Invoked when the favicon is available. If the node is a child of the + // root node, the appropriate button is updated. If a menu is showing, the + // call is forwarded to the menu to allow for it to update the icon. + virtual void BookmarkNodeFavIconLoaded(BookmarkBarModel* model, + BookmarkBarNode* node); + + // DragController method. Determines the node representing sender and invokes + // WriteDragData to write the actual data. + virtual void WriteDragData(ChromeViews::View* sender, + int press_x, + int press_y, + OSExchangeData* data); + + // Writes a BookmarkDragData for node to data. + void WriteDragData(BookmarkBarNode* node, OSExchangeData* data); + + // Returns the drag operations for the specified button. + virtual int GetDragOperations(ChromeViews::View* sender, int x, int y); + + // ViewMenuDelegate method. 3 types of menus may be shown: + // . the menu allowing the user to choose when the bookmark bar is visible. + // . most recently bookmarked menu + // . menu for star groups. + // The latter two are handled by a MenuRunner, which builds the appropriate + // menu. + virtual void RunMenu(ChromeViews::View* view, const CPoint& pt, HWND hwnd); + + // Invoked when a star entry corresponding to a URL on the bookmark bar is + // pressed. Forwards to the PageNavigator to open the URL. + virtual void ButtonPressed(ChromeViews::BaseButton* sender); + + // Invoked for this View, one of the buttons or the 'other' button. Shows the + // appropriate context menu. + virtual void ShowContextMenu(ChromeViews::View* source, + int x, + int y, + bool is_mouse_gesture); + + // Creates the button for rendering the specified bookmark node. + ChromeViews::View* CreateBookmarkButton(BookmarkBarNode* node); + + // COnfigures the button from the specified node. This sets the text, + // and icon. + void ConfigureButton(BookmarkBarNode* node, ChromeViews::TextButton* button); + + // Used when showing the menu allowing the user to choose when the bar is + // visible. Return value corresponds to the users preference for when the + // bar is visible. + virtual bool IsItemChecked(int id) const; + + // Used when showing the menu allowing the user to choose when the bar is + // visible. Updates the preferences to match the users choice as appropriate. + virtual void ExecuteCommand(int id); + + // Notification that the HistoryService is up an running. Removes us as + // a listener on the notification service and invokes + // ProfileHasValidHistoryService. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Invoked when the profile has a history service. Recreates the models. + void ProfileHasValidHistoryService(); + + // If we have registered an observer on the notification service, this + // unregisters it. This does nothing if we have not installed ourself as an + // observer. + void RemoveNotificationObservers(); + + // If the ModelChangedListener is non-null, ModelChanged is invoked on it. + void NotifyModelChanged(); + + // Shows the menu used during drag and drop for the specified node. + void ShowDropFolderForNode(BookmarkBarNode* node); + + // Cancels the timer used to show a drop menu. + void StopShowFolderDropMenuTimer(); + + // Stars the timer used to show a drop menu for node. + void StartShowFolderDropMenuTimer(BookmarkBarNode* node); + + // Returns the drop operation and index for the drop based on the event + // and data. Returns DragDropTypes::DRAG_NONE if not a valid location. + int CalculateDropOperation(const ChromeViews::DropTargetEvent& event, + const BookmarkDragData& data, + int* index, + bool* drop_on, + bool* is_over_overflow, + bool* is_over_other); + + // Invokes CanDropAt to determine if this is a valid location for the data, + // then returns the appropriate drag operation based on the data. + int CalculateDropOperation(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index); + + // Returns true if the specified location is a valid drop location for + // the supplied drag data. + bool CanDropAt(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index); + + // Performs a drop of the specified data at the specified location. Returns + // the result. + int PerformDropImpl(const BookmarkDragData& data, + BookmarkBarNode* parent_node, + int index); + + // Creates a new group/entry for data, and recursively invokes itself for + // all children of data. This is used during drag and drop to clone a + // group from another profile. + void CloneDragData(const BookmarkDragData& data, + BookmarkBarNode* parent, + int index_to_add_at); + + // Returns the index of the first hidden bookmark button. If all buttons are + // visible, this returns GetBookmarkButtonCount(). + int GetFirstHiddenNodeIndex(); + + // If the bookmark bubble is showing this determines which view should throb + // and starts it throbbing. Does nothing if bookmark bubble isn't showing. + void StartThrobbing(); + + // If a button is currently throbbing, it is stopped. If immediate is true + // the throb stops immediately, otherwise it stops after a couple more + // throbs. + void StopThrobbing(bool immediate); + + Profile* profile_; + + // Used for opening urls. + PageNavigator* page_navigator_; + + // Model providing details as to the starred entries/groups that should be + // shown. This is owned by the Profile. + BookmarkBarModel* model_; + + // Used to manage showing a Menu: either for the most recently bookmarked + // entries, or for the a starred group. + scoped_ptr<MenuRunner> menu_runner_; + + // Used when showing a menu for drag and drop. That is, if the user drags + // over a group this becomes non-null and is the MenuRunner used to manage + // the menu showing the contents of the node. + scoped_ptr<MenuRunner> drop_menu_runner_; + + // Shows the other bookmark entries. + ChromeViews::MenuButton* other_bookmarked_button_; + + // ModelChangeListener. + ModelChangedListener* model_changed_listener_; + + // Task used to delay showing of the drop menu. + ShowFolderDropMenuTask* show_folder_drop_menu_task_; + + // Used to track drops on the bookmark bar view. + scoped_ptr<DropInfo> drop_info_; + + // Visible if not all the bookmark buttons fit. + ChromeViews::MenuButton* overflow_button_; + + // If no bookmarks are visible, we show some text explaining the bar. + ChromeViews::Label* instructions_; + + ButtonSeparatorView* bookmarks_separator_view_; + + // Owning browser. + Browser* browser_; + + // Animation controlling showing and hiding of the bar. + scoped_ptr<SlideAnimation> size_animation_; + + // If the bookmark bubble is showing, this is the URL. + GURL bubble_url_; + + // If the bookmark bubble is showing, this is the visible ancestor of the URL. + // The visible ancestor is either the other_bookmarked_button_, + // overflow_button_ or a button on the bar. + ChromeViews::BaseButton* throbbing_view_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ diff --git a/chrome/browser/views/bookmark_bubble_view.cc b/chrome/browser/views/bookmark_bubble_view.cc new file mode 100644 index 0000000..8d8f34e --- /dev/null +++ b/chrome/browser/views/bookmark_bubble_view.cc @@ -0,0 +1,390 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/bookmark_bubble_view.h" + +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/button.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/native_button.h" +#include "chrome/views/text_field.h" +#include "generated_resources.h" + +using ChromeViews::ComboBox; +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; +using ChromeViews::Label; +using ChromeViews::Link; +using ChromeViews::NativeButton; +using ChromeViews::View; + +// Color of the title. +static const SkColor kTitleColor = SkColorSetRGB(6, 45, 117); + +// Padding between "Title:" and the actual title. +static const int kTitlePadding = 4; + +// Minimum width for the fields - they will push out the size of the bubble if +// necessary. This should be big enough so that the field pushes the right side +// of the bubble far enough so that the edit button's left edge is to the right +// of the field's left edge. +static const int kMinimumFieldSize = 180; + +// Max number of most recently used folders. +static const size_t kMaxMRUFolders = 5; + +// Bubble close image. +static SkBitmap* kCloseImage = NULL; + +// RecentlyUsedFoldersModel --------------------------------------------------- + +BookmarkBubbleView::RecentlyUsedFoldersModel::RecentlyUsedFoldersModel( + BookmarkBarModel* bb_model, BookmarkBarNode* node) + // Use + 2 to account for bookmark bar and other node. + : nodes_(bb_model->GetMostRecentlyModifiedGroups(kMaxMRUFolders + 2)), + node_parent_index_(0) { + // TODO(sky): bug 1173415 add a separator in the combobox here. + + // We special case the placement of these, so remove them from the list, then + // fix up the order. + RemoveNode(bb_model->GetBookmarkBarNode()); + RemoveNode(bb_model->other_node()); + RemoveNode(node->GetParent()); + + // Make the parent the first item, unless it's the bookmark bar or other node. + if (node->GetParent() != bb_model->GetBookmarkBarNode() && + node->GetParent() != bb_model->other_node()) { + nodes_.insert(nodes_.begin(), node->GetParent()); + } + + // Make sure we only have kMaxMRUFolders in the first chunk. + if (nodes_.size() > kMaxMRUFolders) + nodes_.erase(nodes_.begin() + kMaxMRUFolders, nodes_.end()); + + // And put the bookmark bar and other nodes at the end of the list. + nodes_.push_back(bb_model->GetBookmarkBarNode()); + nodes_.push_back(bb_model->other_node()); + + node_parent_index_ = static_cast<int>( + find(nodes_.begin(), nodes_.end(), node->GetParent()) - nodes_.begin()); +} + +int BookmarkBubbleView::RecentlyUsedFoldersModel::GetItemCount( + ComboBox* source) { + return static_cast<int>(nodes_.size() + 1); +} + +std::wstring BookmarkBubbleView::RecentlyUsedFoldersModel::GetItemAt( + ComboBox* source, int index) { + if (index == nodes_.size()) + return l10n_util::GetString(IDS_BOOMARK_BUBBLE_CHOOSER_ANOTHER_FOLDER); + return nodes_[index]->GetTitle(); +} + +BookmarkBarNode* BookmarkBubbleView::RecentlyUsedFoldersModel::GetNodeAt( + int index) { + return nodes_[index]; +} + +void BookmarkBubbleView::RecentlyUsedFoldersModel::RemoveNode( + BookmarkBarNode* node) { + std::vector<BookmarkBarNode*>::iterator i = + find(nodes_.begin(), nodes_.end(), node); + if (i != nodes_.end()) + nodes_.erase(i); +} + +// BookmarkBubbleView --------------------------------------------------------- + +// static +void BookmarkBubbleView::Show(HWND parent, + const gfx::Rect& bounds, + InfoBubbleDelegate* delegate, + Profile* profile, + const GURL& url, + bool newly_bookmarked) { + BookmarkBubbleView* view = new BookmarkBubbleView(delegate, profile, url, + newly_bookmarked); + InfoBubble::Show(parent, bounds, view, view); + GURL url_ptr(url); + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_BUBBLE_SHOWN, + Source<Profile>(profile->GetOriginalProfile()), + Details<GURL>(&url_ptr)); + view->BubbleShown(); +} + +BookmarkBubbleView::~BookmarkBubbleView() { + SetNodeTitleFromTextField(); +} + +void BookmarkBubbleView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void BookmarkBubbleView::BubbleShown() { + DCHECK(GetViewContainer()); + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(GetViewContainer()->GetHWND()); + focus_manager->RegisterAccelerator( + ChromeViews::Accelerator(VK_RETURN, false, false, false), this); + + title_tf_->RequestFocus(); + title_tf_->SelectAll(); +} + +bool BookmarkBubbleView::AcceleratorPressed( + const ChromeViews::Accelerator& accelerator) { + if (accelerator.GetKeyCode() != VK_RETURN) + return false; + + Close(); + return true; +} + +BookmarkBubbleView::BookmarkBubbleView(InfoBubbleDelegate* delegate, + Profile* profile, + const GURL& url, + bool newly_bookmarked) + : delegate_(delegate), + profile_(profile), + url_(url), + newly_bookmarked_(newly_bookmarked), + parent_model_(profile_->GetBookmarkBarModel(), + profile_->GetBookmarkBarModel()->GetNodeByURL(url)) { + Init(); +} + +void BookmarkBubbleView::Init() { + if (!kCloseImage) { + kCloseImage = ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INFO_BUBBLE_CLOSE); + } + + remove_link_ = new Link(l10n_util::GetString( + IDS_BOOMARK_BUBBLE_REMOVE_BOOKMARK)); + remove_link_->SetController(this); + + edit_button_ = new NativeButton( + l10n_util::GetString(IDS_BOOMARK_BUBBLE_OPTIONS)); + edit_button_->SetListener(this); + + close_button_ = new NativeButton(l10n_util::GetString(IDS_CLOSE)); + close_button_->SetListener(this); + + parent_combobox_ = new ComboBox(&parent_model_); + parent_combobox_->SetSelectedItem(parent_model_.node_parent_index()); + parent_combobox_->SetListener(this); + + Label* title_label = new Label(l10n_util::GetString( + newly_bookmarked_ ? IDS_BOOMARK_BUBBLE_PAGE_BOOKMARKED : + IDS_BOOMARK_BUBBLE_PAGE_BOOKMARK)); + title_label->SetFont( + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont)); + title_label->SetColor(kTitleColor); + + GridLayout* layout = new GridLayout(this); + SetLayoutManager(layout); + + ColumnSet* cs = layout->AddColumnSet(0); + + // Top (title) row. + cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, + 0, 0); + cs->AddPaddingColumn(1, kUnrelatedControlHorizontalSpacing); + cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF, + 0, 0); + + // Middle (input field) rows. + cs = layout->AddColumnSet(2); + cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + cs->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, kMinimumFieldSize); + + // Bottom (buttons) row. + cs = layout->AddColumnSet(3); + cs->AddPaddingColumn(1, kRelatedControlHorizontalSpacing); + cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, + GridLayout::USE_PREF, 0, 0); + // We subtract 2 to account for the natural button padding, and + // to bring the separation visually in line with the row separation + // height. + cs->AddPaddingColumn(0, kRelatedButtonHSpacing - 2); + cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + layout->AddView(title_label); + layout->AddView(remove_link_); + + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, 2); + layout->AddView( + new Label(l10n_util::GetString(IDS_BOOMARK_BUBBLE_TITLE_TEXT))); + title_tf_ = new ChromeViews::TextField(); + title_tf_->SetText(GetTitle()); + layout->AddView(title_tf_); + + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + + layout->StartRow(0, 2); + layout->AddView( + new Label(l10n_util::GetString(IDS_BOOMARK_BUBBLE_FOLDER_TEXT))); + layout->AddView(parent_combobox_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + + layout->StartRow(0, 3); + layout->AddView(edit_button_); + layout->AddView(close_button_); +} + +std::wstring BookmarkBubbleView::GetTitle() { + BookmarkBarModel* bookmark_model= profile_->GetBookmarkBarModel(); + BookmarkBarNode* node = bookmark_model->GetNodeByURL(url_); + if (node) + return node->GetTitle(); + else + NOTREACHED(); + return std::wstring(); +} + +void BookmarkBubbleView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == edit_button_) { + UserMetrics::RecordAction(L"BookmarkBubble_Edit", profile_); + ShowEditor(); + } else { + DCHECK(sender == close_button_); + Close(); + } + // WARNING: we've most likely been deleted when CloseWindow returns. +} + +void BookmarkBubbleView::LinkActivated(Link* source, int event_flags) { + DCHECK(source == remove_link_); + RemoveBookmark(); +} + +void BookmarkBubbleView::ItemChanged(ComboBox* combo_box, + int prev_index, + int new_index) { + if (new_index + 1 == parent_model_.GetItemCount(parent_combobox_)) { + UserMetrics::RecordAction(L"BookmarkBubble_EditFromCombobox", profile_); + + ShowEditor(); + return; + } + BookmarkBarModel* model = profile_->GetBookmarkBarModel(); + BookmarkBarNode* node = model->GetNodeByURL(url_); + if (node) { + BookmarkBarNode* new_parent = parent_model_.GetNodeAt(new_index); + if (new_parent != node->GetParent()) { + UserMetrics::RecordAction(L"BookmarkBubble_ChangeParent", profile_); + model->Move(node, new_parent, new_parent->GetChildCount()); + } + } +} + +void BookmarkBubbleView::InfoBubbleClosing(InfoBubble* info_bubble) { + if (delegate_) + delegate_->InfoBubbleClosing(info_bubble); + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_BUBBLE_HIDDEN, + Source<Profile>(profile_->GetOriginalProfile()), + NotificationService::NoDetails()); +} + +bool BookmarkBubbleView::CloseOnEscape() { + return delegate_ ? delegate_->CloseOnEscape() : true; +} + +void BookmarkBubbleView::Close() { + static_cast<InfoBubble*>(GetViewContainer())->Close(); +} + +void BookmarkBubbleView::RemoveBookmark() { + UserMetrics::RecordAction(L"BookmarkBubble_Unstar", profile_); + + GURL url = url_; + BookmarkBarModel* model = profile_->GetBookmarkBarModel(); + // Close first, then notify the service. That way we know we won't be + // visible and don't have to worry about some other window becoming + // activated and deleting us before we invoke Close. + Close(); + // WARNING: we've likely been deleted. + if (model) + model->SetURLStarred(url, std::wstring(), false); +} + +void BookmarkBubbleView::ShowEditor() { + // Parent the editor to our root ancestor (not the root we're in, as that + // is the info bubble and will close shortly). + HWND parent = GetAncestor(GetViewContainer()->GetHWND(), GA_ROOTOWNER); + + // We're about to show the bookmark editor. When the bookmark editor closes + // we want the browser to become active. HWNDViewContainer::Hide() does a + // hide in a such way that activation isn't changed, which means when we + // close Windows gets confused as to who it should give active status to. + // We explicitly hide the bookmark bubble window in such a way that + // activation status changes. That way, when the editor closes, activation + // is properly restored to the browser. + ShowWindow(GetViewContainer()->GetHWND(), SW_HIDE); + + // Even though we just hid the window, we need to invoke Close to schedule + // the delete and all that. + Close(); + + BookmarkEditorView::Show(parent, profile_, url_, title_); +} + +void BookmarkBubbleView::SetNodeTitleFromTextField() { + BookmarkBarModel* model = profile_->GetBookmarkBarModel(); + BookmarkBarNode* node = model->GetNodeByURL(url_); + if (node) { + const std::wstring new_title = title_tf_->GetText(); + if (new_title != node->GetTitle()) { + model->SetTitle(node, new_title); + UserMetrics::RecordAction(L"BookmarkBubble_ChangeTitleInBubble", + profile_); + } + } +}
\ No newline at end of file diff --git a/chrome/browser/views/bookmark_bubble_view.h b/chrome/browser/views/bookmark_bubble_view.h new file mode 100644 index 0000000..53d95fd --- /dev/null +++ b/chrome/browser/views/bookmark_bubble_view.h @@ -0,0 +1,187 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_BUBBLE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_BOOKMARK_BUBBLE_VIEW_H__ + +#include "base/gfx/rect.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/link.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" +#include "googleurl/src/gurl.h" + +class Profile; + +class BookmarkBarModel; +class BookmarkBarNode; + +namespace ChromeViews { +class CheckBox; +class TextField; +} + +// BookmarkBubbleView is a view intended to be used as the content of an +// InfoBubble. BookmarkBubbleView provides views for unstarring and editting +// the bookmark it is created with. Don't create a BookmarkBubbleView directly, +// instead use the static Show method. +class BookmarkBubbleView : public ChromeViews::View, + public ChromeViews::LinkController, + public ChromeViews::NativeButton::Listener, + public ChromeViews::ComboBox::Listener, + public InfoBubbleDelegate { + public: + static void Show(HWND parent, + const gfx::Rect& bounds, + InfoBubbleDelegate* delegate, + Profile* profile, + const GURL& url, + bool newly_bookmarked); + + virtual ~BookmarkBubbleView(); + + // Overriden to force a layout. + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Invoked after the bubble has been shown. + virtual void BubbleShown(); + + // Override to close on return. + virtual bool AcceleratorPressed(const ChromeViews::Accelerator& accelerator); + + private: + // Model for the combobox showing the list of folders to choose from. The + // list always contains the bookmark bar, other node and parent. The list + // also contains an extra item that shows the text 'Choose another folder...'. + class RecentlyUsedFoldersModel : public ChromeViews::ComboBox::Model { + public: + RecentlyUsedFoldersModel(BookmarkBarModel* bb_model, BookmarkBarNode* node); + + // ComboBox::Model methods. Call through to nodes_. + virtual int GetItemCount(ChromeViews::ComboBox* source); + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Returns the node at the specified index. + BookmarkBarNode* GetNodeAt(int index); + + // Returns the index of the original parent folder. + int node_parent_index() const { return node_parent_index_; } + + private: + // Removes node from nodes_. Does nothing if node is not in nodes_. + void RemoveNode(BookmarkBarNode* node); + + std::vector<BookmarkBarNode*> nodes_; + int node_parent_index_; + + DISALLOW_EVIL_CONSTRUCTORS(RecentlyUsedFoldersModel); + }; + + // Creates a BookmarkBubbleView. + // |title| is the title of the page. If newly_bookmarked is false, title is + // ignored and the title of the bookmark is fetched from the database. + BookmarkBubbleView(InfoBubbleDelegate* delegate, + Profile* profile, + const GURL& url, + bool newly_bookmarked); + // Creates the child views. + void Init(); + + // Returns the title to display. + std::wstring GetTitle(); + + // LinkController method, either unstars the item or shows the bookmark + // editor (depending upon which link was clicked). + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + // ButtonListener method, closes the bubble or opens the edit dialog. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ComboBox::Listener method. Changes the parent of the bookmark. + virtual void ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index); + + // InfoBubbleDelegate methods. These forward to the InfoBubbleDelegate + // supplied in the constructor as well as sending out the necessary + // notification. + virtual void InfoBubbleClosing(InfoBubble* info_bubble); + virtual bool CloseOnEscape(); + + // Closes the bubble. + void Close(); + + // Removes the bookmark and closes the view. + void RemoveBookmark(); + + // Shows the BookmarkEditor. + void ShowEditor(); + + // Sets the title of the bookmark from the editor + void SetNodeTitleFromTextField(); + + // Delegate for the bubble, may be null. + InfoBubbleDelegate* delegate_; + + // The profile. + Profile* profile_; + + // The bookmark URL. + const GURL url_; + + // Title of the bookmark. This is initially the title supplied to the + // constructor, which is typically the title of the page. + std::wstring title_; + + // If true, the page was just bookmarked. + const bool newly_bookmarked_; + + RecentlyUsedFoldersModel parent_model_; + + // Link for removing/unstarring the bookmark. + ChromeViews::Link* remove_link_; + + // Button to bring up the editor. + ChromeViews::NativeButton* edit_button_; + + // Button to close the window. + ChromeViews::NativeButton* close_button_; + + // TextField showing the title of the bookmark. + ChromeViews::TextField* title_tf_; + + // ComboBox showing a handful of folders the user can choose from, including + // the current parent. + ChromeViews::ComboBox* parent_combobox_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkBubbleView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_BUBBLE_VIEW_H__ diff --git a/chrome/browser/views/bookmark_editor_view.cc b/chrome/browser/views/bookmark_editor_view.cc new file mode 100644 index 0000000..e7a834a --- /dev/null +++ b/chrome/browser/views/bookmark_editor_view.cc @@ -0,0 +1,561 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/bookmark_editor_view.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/url_fixer_upper.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/background.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/window.h" +#include "generated_resources.h" +#include "googleurl/src/gurl.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; +using ChromeViews::Label; +using ChromeViews::NativeButton; +using ChromeViews::TextField; +using ChromeViews::TreeNode; + +// Background color of text field when URL is invalid. +static const SkColor kErrorColor = SkColorSetRGB(0xFF, 0xBC, 0xBC); + +// Preferred width of the tree. +static const int kTreeWidth = 300; + +// ID for various children. +static const int kNewGroupButtonID = 1002; + +// static +void BookmarkEditorView::Show(HWND parent_hwnd, + Profile* profile, + const GURL& url, + const std::wstring& title) { + DCHECK(profile); + BookmarkEditorView* editor = new BookmarkEditorView(profile, url, title); + editor->Show(parent_hwnd); +} + +BookmarkEditorView::BookmarkEditorView(Profile* profile, + const GURL& url, + const std::wstring& title) + : profile_(profile), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + new_group_button_( + l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON)), + dialog_(NULL), + url_(url), + title_(title), + running_menu_for_root_(false) { + DCHECK(profile); + Init(); +} + +BookmarkEditorView::~BookmarkEditorView() { + bb_model_->RemoveObserver(this); +} + +bool BookmarkEditorView::IsDialogButtonEnabled(DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + const GURL url(GetInputURL()); + return bb_model_->IsLoaded() && url.is_valid(); + } + return true; +} +bool BookmarkEditorView::IsModal() const { + return true; +} + +std::wstring BookmarkEditorView::GetWindowTitle() const { + return l10n_util::GetString(IDS_BOOMARK_EDITOR_TITLE); +} + +bool BookmarkEditorView::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) { + // The url is invalid, focus the url field. + url_tf_.SelectAll(); + url_tf_.RequestFocus(); + return false; + } + // Otherwise save changes and close the dialog box. + ApplyEdits(); + return true; +} + +bool BookmarkEditorView::AreAcceleratorsEnabled(DialogButton button) { + return !tree_view_.GetEditingNode(); +} + +void BookmarkEditorView::Layout() { + // Let the grid layout manager lay out most of the dialog... + GetLayoutManager()->Layout(this); + + // Manually lay out the New Folder button in the same row as the OK/Cancel + // buttons... + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + CSize prefsize; + new_group_button_.GetPreferredSize(&prefsize); + int button_y = parent_bounds.bottom - prefsize.cy - kButtonVEdgeMargin; + new_group_button_.SetBounds(kPanelHorizMargin, button_y, prefsize.cx, + prefsize.cy); +} + +void BookmarkEditorView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS, + IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void BookmarkEditorView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void BookmarkEditorView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (child == this) { + // Add and remove the New Folder button from the ClientView's hierarchy. + if (is_add) { + parent->AddChildView(&new_group_button_); + } else { + parent->RemoveChildView(&new_group_button_); + } + } +} + +void BookmarkEditorView::OnTreeViewSelectionChanged( + ChromeViews::TreeView* tree_view) { +} + +bool BookmarkEditorView::CanEdit(ChromeViews::TreeView* tree_view, + ChromeViews::TreeModelNode* node) { + // Only allow editting of children of the bookmark bar node and other node. + BookmarkNode* bb_node = tree_model_->AsNode(node); + return (bb_node->GetParent() && bb_node->GetParent()->GetParent()); +} + +void BookmarkEditorView::ContentsChanged(TextField* sender, + const std::wstring& new_contents) { + UserInputChanged(); +} + +void BookmarkEditorView::ButtonPressed(NativeButton* sender) { + DCHECK(sender); + switch (sender->GetID()) { + case kNewGroupButtonID: + NewGroup(); + break; + + default: + NOTREACHED(); + } +} + +void BookmarkEditorView::ExecuteCommand(int id) { + DCHECK(tree_view_.GetSelectedNode()); + if (id == IDS_EDIT) { + tree_view_.StartEditing(tree_view_.GetSelectedNode()); + } else { + DCHECK(id == IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM); + NewGroup(); + } +} + +bool BookmarkEditorView::IsCommandEnabled(int id) const { + return (id != IDS_EDIT || !running_menu_for_root_); +} + +void BookmarkEditorView::Show(HWND parent_hwnd) { + dialog_ = ChromeViews::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), + this, this); + UserInputChanged(); + if (bb_model_->IsLoaded()) + ExpandAndSelect(); + dialog_->Show(); + // Select all the text in the name textfield. + title_tf_.SelectAll(); + // Give focus to the name textfield. + title_tf_.RequestFocus(); +} + +void BookmarkEditorView::Close() { + DCHECK(dialog_); + dialog_->Close(); +} + +void BookmarkEditorView::ShowContextMenu(View* source, + int x, + int y, + bool is_mouse_gesture) { + DCHECK(source == &tree_view_); + if (!tree_view_.GetSelectedNode()) + return; + running_menu_for_root_ = + (tree_model_->GetParent(tree_view_.GetSelectedNode()) == + tree_model_->GetRoot()); + context_menu_.reset(new Menu(this, Menu::TOPLEFT, + GetViewContainer()->GetHWND())); + context_menu_->AppendMenuItemWithLabel(IDS_EDIT, + l10n_util::GetString(IDS_EDIT)); + context_menu_->AppendMenuItemWithLabel( + IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM, + l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM)); + context_menu_->RunMenuAt(x, y); +} + +void BookmarkEditorView::Init() { + tree_view_.SetContextMenuController(this); + bb_model_ = profile_->GetBookmarkBarModel(); + DCHECK(bb_model_); + bb_model_->AddObserver(this); + + tree_view_.SetRootShown(false); + // Tell View not to delete all Views declared by value. + tree_view_.SetParentOwned(false); + new_group_button_.SetParentOwned(false); + url_tf_.SetParentOwned(false); + title_tf_.SetParentOwned(false); + + new_group_button_.SetEnabled(false); + new_group_button_.SetListener(this); + new_group_button_.SetID(kNewGroupButtonID); + + title_tf_.SetText(title_); + title_tf_.SetController(this); + + url_tf_.SetText(UTF8ToWide(url_.spec())); + url_tf_.SetController(this); + + // Yummy layout code. + const int labels_column_set_id = 0; + const int single_column_view_set_id = 1; + const int buttons_column_set_id = 2; + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + ColumnSet* column_set = layout->AddColumnSet(labels_column_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, kTreeWidth, 0); + + column_set = layout->AddColumnSet(buttons_column_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(1, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + column_set->LinkColumnSizes(0, 2, 4, -1); + + layout->StartRow(0, labels_column_set_id); + layout->AddView( + new Label(l10n_util::GetString(IDS_BOOMARK_EDITOR_NAME_LABEL))); + layout->AddView(&title_tf_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, labels_column_set_id); + layout->AddView( + new Label(l10n_util::GetString(IDS_BOOMARK_EDITOR_URL_LABEL))); + layout->AddView(&url_tf_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(1, single_column_view_set_id); + layout->AddView(&tree_view_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + if (bb_model_->IsLoaded()) + Loaded(bb_model_); +} + +void BookmarkEditorView::Loaded(BookmarkBarModel* model) { + Reset(true); +} + +void BookmarkEditorView::BookmarkNodeMoved(BookmarkBarModel* model, + BookmarkBarNode* old_parent, + int old_index, + BookmarkBarNode* new_parent, + int new_index) { + Reset(false); +} + +void BookmarkEditorView::BookmarkNodeAdded(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + Reset(false); +} + +void BookmarkEditorView::BookmarkNodeRemoved(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index) { + Reset(false); +} + +void BookmarkEditorView::Reset(bool first_time) { + BookmarkBarNode* node_editing = bb_model_->GetNodeByURL(url_); + + // If the title is empty we need to fetch it from the node. + if (first_time && title_.empty()) { + if (node_editing) { + title_ = node_editing->GetTitle(); + title_tf_.SetText(title_); + } + } + + // Do this first, otherwise when we invoke SetModel with the real one + // tree_view will try to invoke something on the model we just deleted. + tree_view_.SetModel(NULL); + + BookmarkNode* root_node = CreateRootNode(); + tree_model_.reset(new BookmarkTreeModel(root_node)); + + tree_view_.SetModel(tree_model_.get()); + tree_view_.SetController(this); + + new_group_button_.SetEnabled(true); + + context_menu_.reset(); + + if (GetParent()) { + ExpandAndSelect(); + + if(!first_time) + UserInputChanged(); + } else if (GetParent()) { + tree_view_.ExpandAll(); + } +} +GURL BookmarkEditorView::GetInputURL() const { + std::wstring input = URLFixerUpper::FixupURL(url_tf_.GetText(), L""); + return GURL(input); +} + +std::wstring BookmarkEditorView::GetInputTitle() const { + return title_tf_.GetText(); +} + +void BookmarkEditorView::UserInputChanged() { + const GURL url(GetInputURL()); + if (!url.is_valid()) + url_tf_.SetBackgroundColor(kErrorColor); + else + url_tf_.SetDefaultBackgroundColor(); + dialog_->UpdateDialogButtons(); +} + +void BookmarkEditorView::NewGroup() { + // Create a new entry parented to the selected item, or the bookmark + // bar if nothing is selected. + BookmarkNode* parent = tree_model_->AsNode(tree_view_.GetSelectedNode()); + if (!parent) { + NOTREACHED(); + return; + } + + BookmarkNode* new_node = new BookmarkNode(); + new_node->SetTitle(l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME)); + new_node->value = 0; + // new_node is now owned by parent. + tree_model_->Add(parent, parent->GetChildCount(), new_node); + // Edit the new node. + tree_view_.StartEditing(new_node); +} + +void BookmarkEditorView::ExpandAndSelect() { + tree_view_.ExpandAll(); + + BookmarkBarNode* to_select = bb_model_->GetNodeByURL(url_); + history::UIStarID group_id_to_select = + to_select ? to_select->GetParent()->GetGroupID() : + bb_model_->GetParentForNewNodes()->GetGroupID(); + + DCHECK(group_id_to_select); // GetMostRecentParent should never return NULL. + BookmarkNode* b_node = + FindNodeWithID(tree_model_->GetRoot(), group_id_to_select); + if (!b_node) + b_node = tree_model_->GetRoot()->GetChild(0); // Bookmark bar node. + + tree_view_.SetSelectedNode(b_node); +} + +BookmarkEditorView::BookmarkNode* BookmarkEditorView::CreateRootNode() { + BookmarkNode* root_node = new BookmarkNode(std::wstring(), 0); + BookmarkBarNode* bb_root_node = bb_model_->root_node(); + CreateNodes(bb_root_node, root_node); + DCHECK(root_node->GetChildCount() == 2); + DCHECK(bb_root_node->GetChild(0)->GetType() == + history::StarredEntry::BOOKMARK_BAR); + DCHECK(bb_root_node->GetChild(1)->GetType() == history::StarredEntry::OTHER); + return root_node; +} + +void BookmarkEditorView::CreateNodes(BookmarkBarNode* bb_node, + BookmarkEditorView::BookmarkNode* b_node) { + for (int i = 0; i < bb_node->GetChildCount(); ++i) { + BookmarkBarNode* child_bb_node = bb_node->GetChild(i); + if (child_bb_node->GetType() != history::StarredEntry::URL) { + BookmarkNode* new_b_node = new BookmarkNode(child_bb_node->GetTitle(), + child_bb_node->GetGroupID()); + b_node->Add(b_node->GetChildCount(), new_b_node); + CreateNodes(child_bb_node, new_b_node); + } + } +} + +BookmarkEditorView::BookmarkNode* BookmarkEditorView::FindNodeWithID( + BookmarkEditorView::BookmarkNode* node, + history::UIStarID id) { + if (node->value == id) + return node; + for (int i = 0; i < node->GetChildCount(); ++i) { + BookmarkNode* result = FindNodeWithID(node->GetChild(i), id); + if (result) + return result; + } + return NULL; +} + +void BookmarkEditorView::ApplyEdits() { + DCHECK(bb_model_->IsLoaded()); + + if (!tree_view_.GetSelectedNode()) { + NOTREACHED(); + return; + } + + // We're going to apply edits to the bookmark bar model, which will call us + // back. Normally when a structural edit occurs we reset the tree model. + // We don't want to do that here, so we remove ourselves as an observer. + bb_model_->RemoveObserver(this); + + GURL new_url(GetInputURL()); + std::wstring new_title(GetInputTitle()); + + BookmarkBarNode* old_node = bb_model_->GetNodeByURL(url_); + BookmarkBarNode* old_parent = old_node ? old_node->GetParent() : NULL; + const int old_index = old_parent ? old_parent->IndexOfChild(old_node) : -1; + + if (url_ != new_url) { + // The URL has changed, unstar the old url. + bb_model_->SetURLStarred(url_, std::wstring(), false); + } + + // Create the new groups and update the titles. + BookmarkBarNode* new_parent = NULL; + ApplyNameChangesAndCreateNewGroups( + bb_model_->root_node(), tree_model_->GetRoot(), + tree_model_->AsNode(tree_view_.GetSelectedNode()), &new_parent); + + if (!new_parent) { + // Bookmarks must be parented. + NOTREACHED(); + return; + } + + BookmarkBarNode* current_node = bb_model_->GetNodeByURL(new_url); + + if (current_node) { + // There's already a node with the URL. + bb_model_->SetTitle(current_node, new_title); + if (new_parent == old_parent) { + // Parent hasn't changed. + bb_model_->Move(current_node, new_parent, old_index); + } else { + // Parent changed, move to end of new parent. + bb_model_->Move(current_node, new_parent, new_parent->GetChildCount()); + } + } else { + // Adding a new URL. + if (new_parent == old_parent) { + // Parent hasn't changed. Place newly created bookmark at the same + // location as last bookmark. + bb_model_->AddURL(new_parent, old_index, new_title, new_url); + } else { + // Parent changed, put bookmark at end of new parent. + bb_model_->AddURL(new_parent, new_parent->GetChildCount(), new_title, + new_url); + } + } +} + +void BookmarkEditorView::ApplyNameChangesAndCreateNewGroups( + BookmarkBarNode* bb_node, + BookmarkEditorView::BookmarkNode* b_node, + BookmarkEditorView::BookmarkNode* parent_b_node, + BookmarkBarNode** parent_bb_node) { + if (parent_b_node == b_node) + *parent_bb_node = bb_node; + for (int i = 0; i < b_node->GetChildCount(); ++i) { + BookmarkNode* child_b_node = b_node->GetChild(i); + BookmarkBarNode* child_bb_node = NULL; + if (child_b_node->value == 0) { + // New group. + child_bb_node = bb_model_->AddGroup(bb_node, + bb_node->GetChildCount(), child_b_node->GetTitle()); + } else { + // Existing node, reset the title (BBModel ignores changes if the title + // is the same). + for (int j = 0; j < bb_node->GetChildCount(); ++j) { + BookmarkBarNode* node = bb_node->GetChild(j); + if (node->GetType() != history::StarredEntry::URL && + node->GetGroupID() == child_b_node->value) { + child_bb_node = node; + break; + } + } + DCHECK(child_bb_node); + bb_model_->SetTitle(child_bb_node, child_b_node->GetTitle()); + } + ApplyNameChangesAndCreateNewGroups(child_bb_node, child_b_node, + parent_b_node, parent_bb_node); + } +} diff --git a/chrome/browser/views/bookmark_editor_view.h b/chrome/browser/views/bookmark_editor_view.h new file mode 100644 index 0000000..c12abeb --- /dev/null +++ b/chrome/browser/views/bookmark_editor_view.h @@ -0,0 +1,269 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_EDITOR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_BOOKMARK_EDITOR_VIEW_H__ + +#include <set> + +#include "chrome/views/tree_node_model.h" +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/menu.h" +#include "chrome/views/native_button.h" +#include "chrome/views/text_field.h" + +namespace ChromeViews { +class Window; +} + +class GURL; +class Menu; +class Profile; + +// View that allows the user to edit a bookmark/starred URL. The user can +// change the URL, title and where the bookmark appears as well as adding +// new groups and changing the name of other groups. +// +// Edits are applied to the BookmarkBarModel when the user presses 'OK'. +// +// To use BookmarkEditorView invoke the static show method. + +class BookmarkEditorView : public ChromeViews::View, + public ChromeViews::NativeButton::Listener, + public ChromeViews::TreeViewController, + public ChromeViews::DialogDelegate, + public ChromeViews::TextField::Controller, + public ChromeViews::ContextMenuController, + public Menu::Delegate, + public BookmarkBarModelObserver { + public: + // Shows the BookmarkEditorView editing the specified entry. + static void Show(HWND parent_window, + Profile* profile, + const GURL& url, + const std::wstring& title); + + BookmarkEditorView(Profile* profile, + const GURL& url, + const std::wstring& title); + + virtual ~BookmarkEditorView(); + + // DialogDelegate methods: + virtual bool IsDialogButtonEnabled(DialogButton button) const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + virtual bool AreAcceleratorsEnabled(DialogButton button); + + // View methods. + virtual void Layout(); + virtual void GetPreferredSize(CSize *out); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void ViewHierarchyChanged(bool is_add, ChromeViews::View* parent, + ChromeViews::View* child); + + // TreeViewObserver methods. + virtual void OnTreeViewSelectionChanged(ChromeViews::TreeView* tree_view); + virtual bool CanEdit(ChromeViews::TreeView* tree_view, + ChromeViews::TreeModelNode* node); + + // TextField::Controller methods. + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags) {} + + // NativeButton/CheckBox. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Menu::Delegate method. + virtual void ExecuteCommand(int id); + + // Menu::Delegate method, return false if id is edit and the bookmark node + // was selected, true otherwise. + virtual bool IsCommandEnabled(int id) const; + + // Creates a Window and adds the BookmarkEditorView to it. When the window is + // closed the BookmarkEditorView is deleted. + void Show(HWND parent_hwnd); + + // Closes the dialog. + void Close(); + + // Shows the context menu. + virtual void ShowContextMenu(View* source, + int x, + int y, + bool is_mouse_gesture); + + private: + // Type of node in the tree. + typedef ChromeViews::TreeNodeWithValue<history::UIStarID> BookmarkNode; + + // Model for the TreeView. Trivial subclass that doesn't allow titles with + // empty strings. + class BookmarkTreeModel : public ChromeViews::TreeNodeModel<BookmarkNode> { + public: + explicit BookmarkTreeModel(BookmarkNode* root) + : TreeNodeModel<BookmarkNode>(root) {} + + virtual void SetTitle(ChromeViews::TreeModelNode* node, + const std::wstring& title) { + if (!title.empty()) + TreeNodeModel::SetTitle(node, title); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(BookmarkTreeModel); + }; + + // Creates the necessary sub-views, configures them, adds them to the layout, + // and requests the entries to display from the database. + void Init(); + + // BookmarkBarModel observer methods. Any structural change results in + // resetting the tree model. + virtual void Loaded(BookmarkBarModel* model); + virtual void BookmarkNodeMoved(BookmarkBarModel* model, + BookmarkBarNode* old_parent, + int old_index, + BookmarkBarNode* new_parent, + int new_index); + virtual void BookmarkNodeAdded(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + virtual void BookmarkNodeRemoved(BookmarkBarModel* model, + BookmarkBarNode* parent, + int index); + virtual void BookmarkNodeChanged(BookmarkBarModel* model, + BookmarkBarNode* node) {} + virtual void BookmarkNodeFavIconLoaded(BookmarkBarModel* model, + BookmarkBarNode* node) {} + + // Resets the model of the tree and updates the various buttons appropriately. + // If first_time is true, Reset is being invoked from the constructor or + // once the bookmark bar has finished loading. + void Reset(bool first_time); + + // Expands all the nodes in the tree and selects the parent node of the + // url we're editing or the most recent parent if the url being editted isn't + // starred. + void ExpandAndSelect(); + + // Creates a returns the new root node. This invokes CreateNodes to do + // the real work. + BookmarkNode* CreateRootNode(); + + // Adds and creates a child node in b_node for all children of bb_node that + // are groups. + void CreateNodes(BookmarkBarNode* bb_node, + BookmarkNode* b_node); + + // Returns the node with the specified id, or NULL if one can't be found. + BookmarkNode* FindNodeWithID(BookmarkEditorView::BookmarkNode* node, + history::UIStarID id); + + // Applies the edits done by the user. + void ApplyEdits(); + + // Recursively adds newly created groups and sets the title of nodes to + // match the user edited title. + // + // bb_node gives the BookmarkBarNode the edits are to be applied to, + // with b_node the source of the edits. + // + // If b_node == parent_b_node, parent_bb_node is set to bb_node. This is + // used to determine the new BookmarkBarNode parent based on the BookmarkNode + // parent. + void ApplyNameChangesAndCreateNewGroups( + BookmarkBarNode* bb_node, + BookmarkEditorView::BookmarkNode* b_node, + BookmarkEditorView::BookmarkNode* parent_b_node, + BookmarkBarNode** parent_bb_node); + + // Returns the current url the user has input. + GURL GetInputURL() const; + + // Returns the title the user has input. + std::wstring GetInputTitle() const; + + // Invoked when the url or title has possibly changed. Updates the background + // of textfields and ok button appropriately. + void UserInputChanged(); + + // Creates a new group as a child of the selected node. If no node is + // selected, the new group is added as a child of the bookmark node. Starts + // editing on the new gorup as well. + void NewGroup(); + + // Profile the entry is from. + Profile* profile_; + + // Model driving the TreeView. + scoped_ptr<BookmarkTreeModel> tree_model_; + + // Displays star groups. + ChromeViews::TreeView tree_view_; + + // Used to create a new group. + ChromeViews::NativeButton new_group_button_; + + // Used for editing the URL. + ChromeViews::TextField url_tf_; + + // Used for editing the title. + ChromeViews::TextField title_tf_; + + // Dialog we're contained in. + ChromeViews::Window* dialog_; + + // URL we were created with. + const GURL url_; + + // The context menu. + scoped_ptr<Menu> context_menu_; + + // Title of the url to display. + std::wstring title_; + + // Mode used to create nodes from. + BookmarkBarModel* bb_model_; + + // If true, we're running the menu for the bookmark bar or other bookmarks + // nodes. + bool running_menu_for_root_; + + DISALLOW_EVIL_CONSTRUCTORS(BookmarkEditorView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_EDITOR_VIEW_H__ diff --git a/chrome/browser/views/bug_report_view.cc b/chrome/browser/views/bug_report_view.cc new file mode 100644 index 0000000..2977409 --- /dev/null +++ b/chrome/browser/views/bug_report_view.cc @@ -0,0 +1,510 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/bug_report_view.h" + +#include <iostream> +#include <fstream> + +#include "base/string_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/safe_browsing/safe_browsing_util.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/url_fetcher.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/client_view.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/Window.h" +#include "generated_resources.h" +#include "net/base/escape.h" +#include "unicode/locid.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; + +// Report a bug data version +static const int kBugReportVersion = 1; + +// Number of lines description field can display at one time. +static const int kDescriptionLines = 5; + +// Google's phishing reporting URL. +static const char kReportPhishingUrl[] = + "http://www.google.com/safebrowsing/report_phish/"; + +class BugReportComboBoxModel : public ChromeViews::ComboBox::Model { + public: + BugReportComboBoxModel() {} + + enum BugType { + PAGE_WONT_LOAD = 0, + PAGE_LOOKS_ODD, + PHISHING_PAGE, + CANT_SIGN_IN, + CHROME_MISBEHAVES, + SOMETHING_MISSING, + BROWSER_CRASH, + OTHER_PROBLEM + }; + + // ChromeViews::ComboBox::Model interface. + virtual int GetItemCount(ChromeViews::ComboBox* source) { + return OTHER_PROBLEM + 1; + } + + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index) { + return GetItemAtIndex(index); + } + + static std::wstring GetItemAtIndex(int index) { + switch (index) { + case PAGE_WONT_LOAD: + return l10n_util::GetString(IDS_BUGREPORT_PAGE_WONT_LOAD); + case PAGE_LOOKS_ODD: + return l10n_util::GetString(IDS_BUGREPORT_PAGE_LOOKS_ODD); + case PHISHING_PAGE: + return l10n_util::GetString(IDS_BUGREPORT_PHISHING_PAGE); + case CANT_SIGN_IN: + return l10n_util::GetString(IDS_BUGREPORT_CANT_SIGN_IN); + case CHROME_MISBEHAVES: + return l10n_util::GetString(IDS_BUGREPORT_CHROME_MISBEHAVES); + case SOMETHING_MISSING: + return l10n_util::GetString(IDS_BUGREPORT_SOMETHING_MISSING); + case BROWSER_CRASH: + return l10n_util::GetString(IDS_BUGREPORT_BROWSER_CRASH); + case OTHER_PROBLEM: + return l10n_util::GetString(IDS_BUGREPORT_OTHER_PROBLEM); + default: + NOTREACHED(); + return std::wstring(); + } + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(BugReportComboBoxModel); +}; + +// Simple URLFetcher::Delegate to clean up URLFetcher on completion +// (since the BugReportView will be gone by then) +class BugReportView::PostCleanup : public URLFetcher::Delegate { + public: + PostCleanup(); + // Overridden from URLFetcher::Delegate + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + private: + DISALLOW_EVIL_CONSTRUCTORS(PostCleanup); +}; + +BugReportView::PostCleanup::PostCleanup() { +} + +void BugReportView::PostCleanup::OnURLFetchComplete( + const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + // delete the URLFetcher + delete source; + // and then delete ourselves + delete this; +} + +// BugReportView - create and submit a bug report from the user. +// This is separate from crash reporting, which is handled by Breakpad. +// +BugReportView::BugReportView(Profile* profile, TabContents* tab) + : dialog_(NULL), + include_page_source_checkbox_(NULL), + include_page_image_checkbox_(NULL), + profile_(profile), + post_url_(l10n_util::GetString(IDS_BUGREPORT_POST_URL)), + tab_(tab), + problem_type_(0) { + DCHECK(profile); + SetupControl(); +} + +BugReportView::~BugReportView() { +} + +void BugReportView::SetupControl() { + bug_type_model_.reset(new BugReportComboBoxModel); + + // Adds all controls. + bug_type_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BUGREPORT_BUG_TYPE)); + bug_type_combo_ = new ChromeViews::ComboBox(bug_type_model_.get()); + bug_type_combo_->SetListener(this); + + page_title_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BUGREPORT_REPORT_PAGE_TITLE)); + page_title_text_ = new ChromeViews::Label(tab_->GetTitle()); + page_url_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BUGREPORT_REPORT_URL_LABEL)); + // page_url_text_'s text (if any) is filled in after dialog creation + page_url_text_ = new ChromeViews::TextField; + page_url_text_->SetController(this); + + description_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BUGREPORT_DESCRIPTION_LABEL)); + description_text_ = + new ChromeViews::TextField(ChromeViews::TextField::STYLE_MULTILINE); + description_text_->SetHeightInLines(kDescriptionLines); + + include_page_source_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_BUGREPORT_INCLUDE_PAGE_SOURCE_CHKBOX)); + include_page_source_checkbox_->SetIsSelected(true); + include_page_image_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_BUGREPORT_INCLUDE_PAGE_IMAGE_CHKBOX)); + include_page_image_checkbox_->SetIsSelected(true); + + // Arranges controls by using GridLayout. + const int column_set_id = 0; + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + ColumnSet* column_set = layout->AddColumnSet(column_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing * 2); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + // Page Title and text. + layout->StartRow(0, column_set_id); + layout->AddView(page_title_label_); + layout->AddView(page_title_text_, 1, 1, GridLayout::LEADING, + GridLayout::FILL); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Bug type and combo box. + layout->StartRow(0, column_set_id); + layout->AddView(bug_type_label_, 1, 1, GridLayout::LEADING, GridLayout::FILL); + layout->AddView(bug_type_combo_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Page URL and text field. + layout->StartRow(0, column_set_id); + layout->AddView(page_url_label_); + layout->AddView(page_url_text_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Description label and text field. + layout->StartRow(0, column_set_id); + layout->AddView(description_label_, 1, 1, GridLayout::LEADING, + GridLayout::LEADING); + layout->AddView(description_text_, 1, 1, GridLayout::FILL, + GridLayout::LEADING); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + // Checkboxes. + // The include page source checkbox is hidden until we can make it work. + // layout->StartRow(0, column_set_id); + // layout->SkipColumns(1); + // layout->AddView(include_page_source_checkbox_); + // layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->SkipColumns(1); + layout->AddView(include_page_image_checkbox_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); +} + +void BugReportView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_BUGREPORT_DIALOG_WIDTH_CHARS, + IDS_BUGREPORT_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void BugReportView::ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index) { + if (new_index == prev_index) + return; + + problem_type_ = new_index; + bool is_phishing_report = new_index == BugReportComboBoxModel::PHISHING_PAGE; + + description_text_->SetEnabled(!is_phishing_report); + description_text_->SetReadOnly(is_phishing_report); + if (is_phishing_report) { + old_report_text_ = description_text_->GetText(); + description_text_->SetText(std::wstring()); + } else if (!old_report_text_.empty()) { + description_text_->SetText(old_report_text_); + old_report_text_.clear(); + } + include_page_source_checkbox_->SetEnabled(!is_phishing_report); + include_page_source_checkbox_->SetIsSelected(!is_phishing_report); + include_page_image_checkbox_->SetEnabled(!is_phishing_report); + include_page_image_checkbox_->SetIsSelected(!is_phishing_report); + + dialog_->UpdateDialogButtons(); +} + +void BugReportView::ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents) { +} + +void BugReportView::HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, + UINT repeat_count, UINT flags) { +} + +std::wstring BugReportView::GetDialogButtonLabel(DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + if (problem_type_ == BugReportComboBoxModel::PHISHING_PAGE) + return l10n_util::GetString(IDS_BUGREPORT_SEND_PHISHING_REPORT); + else + return l10n_util::GetString(IDS_BUGREPORT_SEND_REPORT); + } else { + return std::wstring(); + } +} + +int BugReportView::GetDefaultDialogButton() const { + return DIALOGBUTTON_NONE; +} + +bool BugReportView::CanResize() const { + return false; +} + +bool BugReportView::CanMaximize() const { + return false; +} + +bool BugReportView::IsAlwaysOnTop() const { + return false; +} + +bool BugReportView::HasAlwaysOnTopMenu() const { + return false; +} + +bool BugReportView::IsModal() const { + return true; +} + +std::wstring BugReportView::GetWindowTitle() const { + return l10n_util::GetString(IDS_BUGREPORT_TITLE); +} + +bool BugReportView::Accept() { + if (IsDialogButtonEnabled(DIALOGBUTTON_OK)) { + if (problem_type_ == BugReportComboBoxModel::PHISHING_PAGE) + ReportPhishing(); + else + SendReport(); + } + return true; +} + +void BugReportView::SetUrl(const GURL& url) { + page_url_text_->SetText(UTF8ToWide(url.spec())); +} + +// SetOSVersion copies the maj.minor.build + servicePack_string +// into a string (for Windows only). This should probably be +// in a util somewhere. We currently have +// win_util::GetWinVersion returns WinVersion, which is just +// an enum of 2000, XP, 2003, or VISTA. Not enough detail for +// bug reports +// env_util::GetOperatingSystemVersion returns an std::string +// but doesn't include the build or service pack. That function +// is probably the right one to extend, but will require changing +// all the call sites or making it a wrapper around another util. +void BugReportView::SetOSVersion(std::string *os_version) { + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + if (GetVersionEx(&osvi)) { + *os_version = StringPrintf("%d.%d.%d %S", + osvi.dwMajorVersion, + osvi.dwMinorVersion, + osvi.dwBuildNumber, + osvi.szCSDVersion); + } else { + *os_version = "unknown"; + } +} + +// Create a MIME boundary marker (27 '-' characters followed by 16 hex digits) +void BugReportView::CreateMimeBoundary(std::string *out) { + int r1 = rand(); + int r2 = rand(); + SStringPrintf(out, "---------------------------%08X%08X", r1, r2); +} + +void BugReportView::SendReport() { + std::wstring post_url = l10n_util::GetString(IDS_BUGREPORT_POST_URL); + std::string mime_boundary; + CreateMimeBoundary(&mime_boundary); + + // create a request body and add the mandatory parameters + std::string post_body; + + // If this is an internal Google user, include user name for followup + // TODO: revisit for public release + + if (!strcmp(getenv("USERDOMAIN"), "GOOGLE") && getenv("USERNAME")) { + std::string user_name(getenv("USERNAME")); + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"username\"\r\n\r\n"); + post_body.append(user_name + "\r\n"); + } + + // Add the protocol version: + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"data_version\"\r\n\r\n"); + post_body.append(StringPrintf("%d\r\n", kBugReportVersion)); + + // Add the page title + post_body.append("--" + mime_boundary + "\r\n"); + std::string page_title = WideToUTF8(page_title_text_->GetText()); + post_body.append("Content-Disposition: form-data; " + "name=\"title\"\r\n\r\n"); + post_body.append(page_title + "\r\n"); + + // Add the problem type + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"problem\"\r\n\r\n"); + post_body.append(StringPrintf("%d\r\n", problem_type_)); + + // Add in the URL, if we have one + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"url\"\r\n\r\n"); + + // convert URL to UTF8 + std::string report_url = WideToUTF8(page_url_text_->GetText()); + if (report_url.empty()) { + post_body.append("n/a\r\n"); + } else { + post_body.append(report_url + "\r\n"); + } + + // Add Chrome version + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"chrome_version\"\r\n\r\n"); + + std::string version = WideToUTF8(version_); + if (version.empty()) { + post_body.append("n/a\r\n"); + } else { + post_body.append(version + "\r\n"); + } + + // Add OS version (eg, for WinXP SP2: "5.1.2600 Service Pack 2") + std::string os_version = ""; + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"os_version\"\r\n\r\n"); + SetOSVersion(&os_version); + post_body.append(os_version + "\r\n"); + + // Add locale + Locale locale = Locale::getDefault(); + const char *lang = locale.getLanguage(); + std::string chrome_locale = (lang)? lang:"en"; + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"chrome_locale\"\r\n\r\n"); + post_body.append(chrome_locale + "\r\n"); + + // Add a description if we have one + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; " + "name=\"description\"\r\n\r\n"); + + std::string description = WideToUTF8(description_text_->GetText()); + if (description.empty()) { + post_body.append("n/a\r\n"); + } else { + post_body.append(description + "\r\n"); + } + + // include the page image if we have one + if (include_page_image_checkbox_->IsSelected() && png_data_.get()) { + post_body.append("--" + mime_boundary + "\r\n"); + post_body.append("Content-Disposition: form-data; name=\"screenshot\"; " + "filename=\"screenshot.png\"\r\n"); + post_body.append("Content-Type: application/octet-stream\r\n"); + post_body.append(StringPrintf("Content-Length: %lu\r\n\r\n", + png_data_->size())); + // the following relies on the fact that STL vectors are guaranteed to + // be stored contiguously. + post_body.append(reinterpret_cast<const char *>(&((*png_data_)[0])), + png_data_->size()); + post_body.append("\r\n"); + } + + // TODO(awalker): include the page source if we can get it + if (include_page_source_checkbox_->IsSelected()) { + } + + // terminate the body + post_body.append("--" + mime_boundary + "--\r\n"); + + // We have the body of our POST, so send it off to the server + + URLFetcher* fetcher = new URLFetcher(post_url_, URLFetcher::POST, + new BugReportView::PostCleanup); + fetcher->set_request_context(profile_->GetRequestContext()); + std::string mime_type("multipart/form-data; boundary="); + mime_type += mime_boundary; + fetcher->set_upload_data(mime_type, post_body); + fetcher->Start(); +} + +void BugReportView::ReportPhishing() { + tab_->controller()->LoadURL( + safe_browsing_util::GeneratePhishingReportUrl( + kReportPhishingUrl, WideToNativeMB(page_url_text_->GetText())), + PageTransition::LINK); +} diff --git a/chrome/browser/views/bug_report_view.h b/chrome/browser/views/bug_report_view.h new file mode 100644 index 0000000..f50e63c --- /dev/null +++ b/chrome/browser/views/bug_report_view.h @@ -0,0 +1,154 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_BUGREPORT_VIEW_H__ +#define CHROME_BROWSER_VIEWS_BUGREPORT_VIEW_H__ + +#include "chrome/browser/url_fetcher.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class CheckBox; +class Label; +class Throbber; +class Window; + +} + +class Profile; +class TabContents; +class BugReportComboBoxModel; + +// BugReportView draws the dialog that allows the user to report a +// bug in rendering a particular page (note: this is not a crash +// report, which are handled separately by Breakpad). It packages +// up the URL, a text description, and optionally a screenshot and/or +// the HTML page source, and submits them as an HTTP POST to the +// URL stored in the string resource IDS_BUGREPORT_POST_URL. +// +// Note: The UI team hasn't defined yet how the bug report UI will look like. +// So now use dialog as a placeholder. +class BugReportView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::ComboBox::Listener, + public ChromeViews::TextField::Controller { + public: + explicit BugReportView(Profile* profile, TabContents* tab); + virtual ~BugReportView(); + + void set_dialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + void set_version(const std::wstring& version) { version_ = version; } + // NOTE: set_png_data takes ownership of the vector + void set_png_data(std::vector<unsigned char> *png_data) { + png_data_.reset(png_data); + }; + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + + // ChromeViews::TextField::Controller implementation: + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, + UINT repeat_count, UINT flags); + + // ChromeViews::ComboBox::Listener implementation: + virtual void ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, int new_index); + + // Overridden from ChromeViews::DialogDelegate: + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual int GetDefaultDialogButton() const; + virtual bool CanResize() const; + virtual bool CanMaximize() const; + virtual bool IsAlwaysOnTop() const; + virtual bool HasAlwaysOnTopMenu() const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + + void SetUrl(const GURL& url); + + private: + class PostCleanup; + + // Set OS Version information in a string (maj.minor.build SP) + void SetOSVersion(std::string *os_version); + + // Initializes the controls on the dialog. + void SetupControl(); + // helper function to create a MIME part boundary string + void CreateMimeBoundary(std::string *out); + // Sends the data via an HTTP POST + void SendReport(); + + // Redirects the user to Google's phishing reporting page. + void ReportPhishing(); + + ChromeViews::Label* bug_type_label_; + ChromeViews::ComboBox* bug_type_combo_; + ChromeViews::Label* page_title_label_; + ChromeViews::Label* page_title_text_; + ChromeViews::Label* page_url_label_; + ChromeViews::TextField* page_url_text_; + ChromeViews::Label* description_label_; + ChromeViews::TextField* description_text_; + ChromeViews::CheckBox* include_page_source_checkbox_; + ChromeViews::CheckBox* include_page_image_checkbox_; + + scoped_ptr<BugReportComboBoxModel> bug_type_model_; + ChromeViews::Window* dialog_; + + Profile* profile_; + + std::wstring version_; + scoped_ptr< std::vector<unsigned char> > png_data_; + + GURL post_url_; + + TabContents* tab_; + + // Used to distinguish the report type: Phishing or other. + int problem_type_; + + // Save the description the user types in when we clear the dialog for the + // phishing option. If the user changes the report type back, we reinstate + // their original text so they don't have to type it again. + std::wstring old_report_text_; + + DISALLOW_EVIL_CONSTRUCTORS(BugReportView); +}; + +#endif // CHROME_BROWSER_VIEWS_BUGREPORT_VIEW_H__ diff --git a/chrome/browser/views/clear_browsing_data.cc b/chrome/browser/views/clear_browsing_data.cc new file mode 100644 index 0000000..17341cd --- /dev/null +++ b/chrome/browser/views/clear_browsing_data.cc @@ -0,0 +1,403 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/clear_browsing_data.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/background.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/label.h" +#include "chrome/views/native_button.h" +#include "chrome/views/throbber.h" +#include "chrome/views/window.h" +#include "net/url_request/url_request_context.h" + +#include "generated_resources.h" + +// The combo box is vertically aligned to the 'time-period' label, which makes +// the combo box look a little too close to the check box above it when we use +// standard layout to separate them. We therefore add a little extra margin to +// the label, giving it a little breathing space. +static const int kExtraMarginForTimePeriodLabel = 3; + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, public: + +ClearBrowsingDataView::ClearBrowsingDataView(Profile* profile) + : dialog_(NULL), + del_history_checkbox_(NULL), + del_downloads_checkbox_(NULL), + del_cache_checkbox_(NULL), + del_cookies_checkbox_(NULL), + del_passwords_checkbox_(NULL), + time_period_label_(NULL), + time_period_combobox_(NULL), + delete_in_progress_(false), + profile_(profile) { + DCHECK(profile); + Init(); +} + +ClearBrowsingDataView::~ClearBrowsingDataView(void) { +} + +void ClearBrowsingDataView::Init() { + // Views we will add to the *parent* of this dialog, since it will display + // next to the buttons which we don't draw ourselves. + throbber_.reset(new ChromeViews::Throbber(50, true)); + throbber_->SetParentOwned(false); + throbber_->SetVisible(false); + + status_label_.SetText(l10n_util::GetString(IDS_CLEAR_DATA_DELETING)); + status_label_.SetVisible(false); + status_label_.SetParentOwned(false); + + // Regular view controls we draw by ourself. First, we add the dialog label. + delete_all_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_CLEAR_BROWSING_DATA_LABEL)); + AddChildView(delete_all_label_); + + // Add all the check-boxes. + del_history_checkbox_ = + AddCheckbox(l10n_util::GetString(IDS_DEL_BROWSING_HISTORY_CHKBOX), true); + + del_downloads_checkbox_ = + AddCheckbox(l10n_util::GetString(IDS_DEL_DOWNLOAD_HISTORY_CHKBOX), true); + + del_cache_checkbox_ = + AddCheckbox(l10n_util::GetString(IDS_DEL_CACHE_CHKBOX), true); + + del_cookies_checkbox_ = + AddCheckbox(l10n_util::GetString(IDS_DEL_COOKIES_CHKBOX), true); + + del_passwords_checkbox_ = + AddCheckbox(l10n_util::GetString(IDS_DEL_PASSWORDS_CHKBOX), false); + + // Add a label which appears before the combo box for the time period. + time_period_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_CLEAR_BROWSING_DATA_TIME_LABEL)); + AddChildView(time_period_label_); + + // Add the combo box showing how far back in time we want to delete. + time_period_combobox_ = new ChromeViews::ComboBox(this); + AddChildView(time_period_combobox_); +} + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, ChromeViews::View implementation: + +void ClearBrowsingDataView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_CLEARDATA_DIALOG_WIDTH_CHARS, + IDS_CLEARDATA_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void ClearBrowsingDataView::Layout() { + CSize panel_size; + GetPreferredSize(&panel_size); + + CSize sz; + + // Delete All label goes to the top left corner. + delete_all_label_->GetPreferredSize(&sz); + delete_all_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin, + sz.cx, sz.cy); + + // Check-boxes go beneath it (with a little indentation). + del_history_checkbox_->GetPreferredSize(&sz); + del_history_checkbox_->SetBounds(2 * kPanelHorizMargin, + delete_all_label_->GetY() + + delete_all_label_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, sz.cy); + + del_downloads_checkbox_->GetPreferredSize(&sz); + del_downloads_checkbox_->SetBounds(2 * kPanelHorizMargin, + del_history_checkbox_->GetY() + + del_history_checkbox_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, sz.cy); + + del_cache_checkbox_->GetPreferredSize(&sz); + del_cache_checkbox_->SetBounds(2 * kPanelHorizMargin, + del_downloads_checkbox_->GetY() + + del_downloads_checkbox_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, sz.cy); + + del_cookies_checkbox_->GetPreferredSize(&sz); + del_cookies_checkbox_->SetBounds(2 * kPanelHorizMargin, + del_cache_checkbox_->GetY() + + del_cache_checkbox_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, sz.cy); + + del_passwords_checkbox_->GetPreferredSize(&sz); + del_passwords_checkbox_->SetBounds(2 * kPanelHorizMargin, + del_cookies_checkbox_->GetY() + + del_cookies_checkbox_->GetHeight() + + kRelatedControlVerticalSpacing, + sz.cx, sz.cy); + + // Time period label is next below the combo boxes. + time_period_label_->GetPreferredSize(&sz); + time_period_label_->SetBounds(kPanelHorizMargin, + del_passwords_checkbox_->GetY() + + del_passwords_checkbox_->GetHeight() + + kRelatedControlVerticalSpacing + + kExtraMarginForTimePeriodLabel, + sz.cx, sz.cy); + + // Time period combo box goes on the right of the label, and we align it + // vertically to the label as well. + int label_y_size = sz.cy; + time_period_combobox_->GetPreferredSize(&sz); + time_period_combobox_->SetBounds(time_period_label_->GetX() + + time_period_label_->GetWidth() + + kRelatedControlVerticalSpacing, + time_period_label_->GetY() - + ((sz.cy - label_y_size) / 2), + sz.cx, sz.cy); + + // Get the y-coordinate of our parent so we can position the throbber and + // status message at the bottom of the panel. + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + + throbber_->GetPreferredSize(&sz); + int throbber_topleft_x = kPanelHorizMargin; + int throbber_topleft_y = parent_bounds.bottom - sz.cy - + kButtonVEdgeMargin - 3; + throbber_->SetBounds(throbber_topleft_x, throbber_topleft_y, sz.cx, sz.cy); + + // The status label should be at the bottom of the screen, to the right of + // the throbber. + status_label_.GetPreferredSize(&sz); + int status_label_x = throbber_->GetX() + throbber_->GetWidth() + + kRelatedControlHorizontalSpacing; + status_label_.SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + status_label_.SetBounds(status_label_x, + throbber_topleft_y + 1, + sz.cx, + sz.cy); +} + +void ClearBrowsingDataView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + // Since we want the some of the controls to show up in the same visual row + // as the buttons, which are provided by the framework, we must add the + // buttons to the non-client view, which is the parent of this view. + // Similarly, when we're removed from the view hierarchy, we must take care + // to remove these items as well. + if (child == this) { + if (is_add) { + parent->AddChildView(&status_label_); + parent->AddChildView(throbber_.get()); + } else { + parent->RemoveChildView(&status_label_); + parent->RemoveChildView(throbber_.get()); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, ChromeViews::DialogDelegate implementation: + +std::wstring ClearBrowsingDataView::GetDialogButtonLabel( + DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + return l10n_util::GetString(IDS_CLEAR_BROWSING_DATA_COMMIT); + } else { + return std::wstring(); + } +} + +bool ClearBrowsingDataView::IsDialogButtonEnabled(DialogButton button) const { + if (delete_in_progress_) + return false; + + if (button == DIALOGBUTTON_OK) { + return del_history_checkbox_->IsSelected() || + del_downloads_checkbox_->IsSelected() || + del_cache_checkbox_->IsSelected() || + del_cookies_checkbox_->IsSelected() || + del_passwords_checkbox_->IsSelected(); + } + + return true; +} + +bool ClearBrowsingDataView::CanResize() const { + return false; +} + +bool ClearBrowsingDataView::CanMaximize() const { + return false; +} + +bool ClearBrowsingDataView::IsAlwaysOnTop() const { + return false; +} + +bool ClearBrowsingDataView::HasAlwaysOnTopMenu() const { + return false; +} + +bool ClearBrowsingDataView::IsModal() const { + return true; +} + +std::wstring ClearBrowsingDataView::GetWindowTitle() const { + return l10n_util::GetString(IDS_CLEAR_BROWSING_DATA_TITLE); +} + +bool ClearBrowsingDataView::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) { + return false; + } + + OnDelete(); + return false; // We close the dialog in OnDeletionDone(). +} + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, ChromeViews::ComboBox::Model implementation: + +int ClearBrowsingDataView::GetItemCount(ChromeViews::ComboBox* source) { + DCHECK(source == time_period_combobox_); + return 4; +} + +std::wstring ClearBrowsingDataView::GetItemAt(ChromeViews::ComboBox* source, + int index) { + DCHECK(source == time_period_combobox_); + switch (index) { + case 0: return l10n_util::GetString(IDS_CLEAR_DATA_DAY); + case 1: return l10n_util::GetString(IDS_CLEAR_DATA_WEEK); + case 2: return l10n_util::GetString(IDS_CLEAR_DATA_4WEEKS); + case 3: return l10n_util::GetString(IDS_CLEAR_DATA_EVERYTHING); + default: NOTREACHED() << L"Missing item"; + return L"?"; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, ChromeViews::ButtonListener implementation: + +void ClearBrowsingDataView::ButtonPressed(ChromeViews::NativeButton* sender) { + // When no checkbox is checked we should not have the action button enabled. + // This forces the button to evaluate what state they should be in. + dialog_->UpdateDialogButtons(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ClearBrowsingDataView, private: + +ChromeViews::CheckBox* ClearBrowsingDataView::AddCheckbox( + const std::wstring& text, bool checked) { + ChromeViews::CheckBox* checkbox = new ChromeViews::CheckBox(text); + checkbox->SetIsSelected(checked); + checkbox->SetListener(this); + AddChildView(checkbox); + return checkbox; +} + +void ClearBrowsingDataView::UpdateControlEnabledState() { + dialog_->EnableClose(!delete_in_progress_); + + del_history_checkbox_->SetEnabled(!delete_in_progress_); + del_downloads_checkbox_->SetEnabled(!delete_in_progress_); + del_cache_checkbox_->SetEnabled(!delete_in_progress_); + del_cookies_checkbox_->SetEnabled(!delete_in_progress_); + del_passwords_checkbox_->SetEnabled(!delete_in_progress_); + time_period_combobox_->SetEnabled(!delete_in_progress_); + + status_label_.SetVisible(delete_in_progress_); + throbber_->SetVisible(delete_in_progress_); + if (delete_in_progress_) + throbber_->Start(); + else + throbber_->Stop(); + + // Make sure to update the state for OK and Cancel buttons. + dialog_->UpdateDialogButtons(); +} + +// Convenience method that returns true if the supplied checkbox is selected +// and enabled. +static bool IsCheckBoxEnabledAndSelected(ChromeViews::CheckBox* cb) { + return (cb->IsEnabled() && cb->IsSelected()); +} + +void ClearBrowsingDataView::OnDelete() { + TimeDelta diff; + Time delete_begin = Time::Now(); + + int period_selected = time_period_combobox_->GetSelectedItem(); + switch (period_selected) { + case 0: diff = TimeDelta::FromHours(24); break; // Last day. + case 1: diff = TimeDelta::FromHours(7*24); break; // Last week. + case 2: diff = TimeDelta::FromHours(4*7*24); break; // Four weeks. + case 3: delete_begin = Time(); break; // Everything. + default: NOTREACHED() << L"Missing item"; break; + } + + delete_begin = delete_begin - diff; + + int remove_mask = 0; + if (IsCheckBoxEnabledAndSelected(del_history_checkbox_)) + remove_mask |= BrowsingDataRemover::REMOVE_HISTORY; + if (IsCheckBoxEnabledAndSelected(del_downloads_checkbox_)) + remove_mask |= BrowsingDataRemover::REMOVE_DOWNLOADS; + if (IsCheckBoxEnabledAndSelected(del_cookies_checkbox_)) + remove_mask |= BrowsingDataRemover::REMOVE_COOKIES; + if (IsCheckBoxEnabledAndSelected(del_passwords_checkbox_)) + remove_mask |= BrowsingDataRemover::REMOVE_PASSWORDS; + if (IsCheckBoxEnabledAndSelected(del_cache_checkbox_)) + remove_mask |= BrowsingDataRemover::REMOVE_CACHE; + + delete_in_progress_ = true; + UpdateControlEnabledState(); + + // BrowsingDataRemover deletes itself when done. + BrowsingDataRemover* remover = + new BrowsingDataRemover(profile_, delete_begin, Time()); + remover->AddObserver(this); + remover->Remove(remove_mask); +} + +void ClearBrowsingDataView::OnBrowsingDataRemoverDone() { + dialog_->Close(); +} diff --git a/chrome/browser/views/clear_browsing_data.h b/chrome/browser/views/clear_browsing_data.h new file mode 100644 index 0000000..bb41ef5 --- /dev/null +++ b/chrome/browser/views/clear_browsing_data.h @@ -0,0 +1,135 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_CLEAR_BROWSING_DATA_H__ +#define CHROME_BROWSER_VIEWS_CLEAR_BROWSING_DATA_H__ + +#include "base/time.h" +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/label.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + class CheckBox; + class Label; + class Throbber; + class Window; +} + +class Profile; +class MessageLoop; + +//////////////////////////////////////////////////////////////////////////////// +// +// The ClearBrowsingData class is responsible for drawing the UI controls of the +// dialog that allows the user to select what to delete (history, downloads, +// etc). +// +//////////////////////////////////////////////////////////////////////////////// +class ClearBrowsingDataView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::ComboBox::Model, + public ChromeViews::NativeButton::Listener, + public BrowsingDataRemover::Observer { + public: + explicit ClearBrowsingDataView(Profile* profile); + virtual ~ClearBrowsingDataView(void); + + // Initialize the controls on the dialog. + void Init(); + + void SetDialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + // Overridden from ChromeViews::DialogDelegate: + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool IsDialogButtonEnabled(DialogButton button) const; + virtual bool CanResize() const; + virtual bool CanMaximize() const; + virtual bool IsAlwaysOnTop() const; + virtual bool HasAlwaysOnTopMenu() const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + + // Overridden from ChromeViews::ComboBox::Model: + virtual int GetItemCount(ChromeViews::ComboBox* source); + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Overridden from ChromeViews::NativeButton::Listener: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + private: + // Adds a new check-box as a child to the view. + ChromeViews::CheckBox* AddCheckbox(const std::wstring& text, bool checked); + + // Sets the controls on the UI to be enabled/disabled depending on whether we + // have a delete operation in progress or not. + void UpdateControlEnabledState(); + + // Starts the process of deleting the browsing data depending on what the + // user selected. + void OnDelete(); + + // Callback from BrowsingDataRemover. Closes the dialog. + virtual void OnBrowsingDataRemoverDone(); + + // UI elements we add to the parent view. + scoped_ptr<ChromeViews::Throbber> throbber_; + ChromeViews::Label status_label_; + // Other UI elements. + ChromeViews::Label* delete_all_label_; + ChromeViews::CheckBox* del_history_checkbox_; + ChromeViews::CheckBox* del_downloads_checkbox_; + ChromeViews::CheckBox* del_cache_checkbox_; + ChromeViews::CheckBox* del_cookies_checkbox_; + ChromeViews::CheckBox* del_passwords_checkbox_; + ChromeViews::Label* time_period_label_; + ChromeViews::ComboBox* time_period_combobox_; + + // Used to signal enabled/disabled state for controls in the UI. + bool delete_in_progress_; + + ChromeViews::Window* dialog_; + + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(ClearBrowsingDataView); +}; + +#endif // CHROME_BROWSER_VIEWS_CLEAR_BROWSING_DATA_H__ diff --git a/chrome/browser/views/constrained_window_animation.cc b/chrome/browser/views/constrained_window_animation.cc new file mode 100644 index 0000000..72d69a0 --- /dev/null +++ b/chrome/browser/views/constrained_window_animation.cc @@ -0,0 +1,55 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/constrained_window_animation.h" +#include "chrome/browser/views/constrained_window_impl.h" + +// The duration of the animation. +static const int kDuration = 360; + +// The frame-rate for the animation. +static const int kFrameRate = 60; + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowAnimation, public: + +ConstrainedWindowAnimation::ConstrainedWindowAnimation( + ConstrainedWindowImpl* window) + : Animation(kDuration, kFrameRate, NULL), window_(window) { +} + +ConstrainedWindowAnimation::~ConstrainedWindowAnimation() { +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowAnimation, Animation implementation: + +void ConstrainedWindowAnimation::AnimateToState(double state) { + window_->SetTitlebarVisibilityPercentage(state); +} diff --git a/chrome/browser/views/constrained_window_animation.h b/chrome/browser/views/constrained_window_animation.h new file mode 100644 index 0000000..02c3017 --- /dev/null +++ b/chrome/browser/views/constrained_window_animation.h @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_CONSTRAINED_WINDOW_ANIMATION_H__ +#define CHROME_BROWSER_VIEWS_CONSTRAINED_WINDOW_ANIMATION_H__ + +#include "chrome/common/animation.h" + +class ConstrainedWindowImpl; + +// Animates a titlebar of a suppressed constrained window up from the +// bottom of the screen. +class ConstrainedWindowAnimation : public Animation { + public: + explicit ConstrainedWindowAnimation(ConstrainedWindowImpl* window); + virtual ~ConstrainedWindowAnimation(); + + // Overridden from Animation: + virtual void AnimateToState(double state); + + private: + // The constrained window we're displaying. + ConstrainedWindowImpl* window_; + + DISALLOW_EVIL_CONSTRUCTORS(ConstrainedWindowAnimation); +}; + +#endif // CHROME_BROWSER_VIEWS_CONSTRAINED_WINDOW_ANIMATION_H__ diff --git a/chrome/browser/views/constrained_window_impl.cc b/chrome/browser/views/constrained_window_impl.cc new file mode 100644 index 0000000..2e7f837 --- /dev/null +++ b/chrome/browser/views/constrained_window_impl.cc @@ -0,0 +1,1493 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/constrained_window_impl.h" + +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/views/constrained_window_animation.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/toolbar_model.h" +#include "chrome/browser/web_app.h" +#include "chrome/browser/web_contents.h" +#include "chrome/browser/window_sizer.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/button.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/hwnd_view.h" +#include "generated_resources.h" +#include "net/base/net_util.h" + +namespace ChromeViews { +class ClientView; +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowResources + +// An enumeration of bitmap resources used by this window. +enum FramePartBitmap { + FRAME_PART_BITMAP_FIRST = 0, // Must be first. + + FRAME_BOTTOM_CENTER, + FRAME_BOTTOM_LEFT_CORNER, + FRAME_BOTTOM_RIGHT_CORNER, + FRAME_LEFT_SIDE, + FRAME_RIGHT_SIDE, + FRAME_TOP_CENTER, + FRAME_TOP_LEFT_CORNER, + FRAME_TOP_RIGHT_CORNER, + + FRAME_CLOSE_BUTTON_ICON, + FRAME_CLOSE_BUTTON_ICON_H, + FRAME_CLOSE_BUTTON_ICON_P, + + FRAME_PART_BITMAP_COUNT // Must be last. +}; + +static const int kXPFramePartIDs[] = { + 0, IDR_CONSTRAINED_BOTTOM_CENTER, IDR_CONSTRAINED_BOTTOM_LEFT_CORNER, + IDR_CONSTRAINED_BOTTOM_RIGHT_CORNER, IDR_CONSTRAINED_LEFT_SIDE, + IDR_CONSTRAINED_RIGHT_SIDE, IDR_CONSTRAINED_TOP_CENTER, + IDR_CONSTRAINED_TOP_LEFT_CORNER, IDR_CONSTRAINED_TOP_RIGHT_CORNER, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, 0 }; +static const int kVistaFramePartIDs[] = { + 0, IDR_CONSTRAINED_BOTTOM_CENTER_V, IDR_CONSTRAINED_BOTTOM_LEFT_CORNER_V, + IDR_CONSTRAINED_BOTTOM_RIGHT_CORNER_V, IDR_CONSTRAINED_LEFT_SIDE_V, + IDR_CONSTRAINED_RIGHT_SIDE_V, IDR_CONSTRAINED_TOP_CENTER_V, + IDR_CONSTRAINED_TOP_LEFT_CORNER_V, IDR_CONSTRAINED_TOP_RIGHT_CORNER_V, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, 0 }; +static const int kOTRFramePartIDs[] = { + 0, IDR_WINDOW_BOTTOM_CENTER_OTR, IDR_WINDOW_BOTTOM_LEFT_CORNER_OTR, + IDR_WINDOW_BOTTOM_RIGHT_CORNER_OTR, IDR_WINDOW_LEFT_SIDE_OTR, + IDR_WINDOW_RIGHT_SIDE_OTR, IDR_WINDOW_TOP_CENTER_OTR, + IDR_WINDOW_TOP_LEFT_CORNER_OTR, IDR_WINDOW_TOP_RIGHT_CORNER_OTR, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, 0 }; + +class WindowResources { + public: + virtual ~WindowResources() {} + virtual SkBitmap* GetPartBitmap(FramePartBitmap part_id) const = 0; + virtual const ChromeFont& GetTitleFont() const = 0; + virtual SkColor GetTitleColor() const = 0; +}; + +class XPWindowResources : public WindowResources { + public: + XPWindowResources() { + InitClass(); + } + virtual ~XPWindowResources() {} + + virtual SkBitmap* GetPartBitmap(FramePartBitmap part_id) const { + return bitmaps_[part_id]; + } + virtual const ChromeFont& GetTitleFont() const { return title_font_; } + virtual SkColor GetTitleColor() const { return SK_ColorWHITE; } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kXPFramePartIDs[i]; + if (id != 0) + bitmaps_[i] = rb.GetBitmapNamed(id); + } + title_font_ = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1); + initialized = true; + } + } + + static SkBitmap* bitmaps_[FRAME_PART_BITMAP_COUNT]; + static ChromeFont title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(XPWindowResources); +}; + +class VistaWindowResources : public WindowResources { + public: + VistaWindowResources() { + InitClass(); + } + virtual ~VistaWindowResources() {} + + virtual SkBitmap* GetPartBitmap(FramePartBitmap part_id) const { + return bitmaps_[part_id]; + } + virtual const ChromeFont& GetTitleFont() const { return title_font_; } + virtual SkColor GetTitleColor() const { return SK_ColorBLACK; } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kVistaFramePartIDs[i]; + if (id != 0) + bitmaps_[i] = rb.GetBitmapNamed(id); + } + title_font_ = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1); + initialized = true; + } + } + + static SkBitmap* bitmaps_[FRAME_PART_BITMAP_COUNT]; + static ChromeFont title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(VistaWindowResources); +}; + +class OTRWindowResources : public WindowResources { + public: + OTRWindowResources() { + InitClass(); + } + virtual ~OTRWindowResources() {} + + virtual SkBitmap* GetPartBitmap(FramePartBitmap part_id) const { + return bitmaps_[part_id]; + } + virtual const ChromeFont& GetTitleFont() const { return title_font_; } + virtual SkColor GetTitleColor() const { return SK_ColorWHITE; } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kOTRFramePartIDs[i]; + if (id != 0) + bitmaps_[i] = rb.GetBitmapNamed(id); + } + title_font_ = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1); + initialized = true; + } + } + + static SkBitmap* bitmaps_[FRAME_PART_BITMAP_COUNT]; + static ChromeFont title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(OTRWindowResources); +}; + +SkBitmap* XPWindowResources::bitmaps_[]; +ChromeFont XPWindowResources::title_font_; +SkBitmap* VistaWindowResources::bitmaps_[]; +ChromeFont VistaWindowResources::title_font_; +SkBitmap* OTRWindowResources::bitmaps_[]; +ChromeFont OTRWindowResources::title_font_; + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView + +class ConstrainedWindowNonClientView + : public ChromeViews::CustomFrameWindow::NonClientView, + public ChromeViews::BaseButton::ButtonListener, + public LocationBarView::Delegate, + public Task { + public: + ConstrainedWindowNonClientView(ConstrainedWindowImpl* container, + TabContents* owner); + virtual ~ConstrainedWindowNonClientView(); + + // Calculates the pixel height of the titlebar + int CalculateTitlebarHeight() const; + + // Calculates the pixel height of all pieces of a window that are + // not part of the webcontent display area. + int CalculateNonClientHeight(bool with_url_field) const; + gfx::Rect CalculateWindowBoundsForClientBounds( + const gfx::Rect& client_bounds, + bool with_url_field) const; + void UpdateWindowIcon(); + void UpdateWindowTitle(); + + void set_window_delegate(ChromeViews::WindowDelegate* window_delegate) { + window_delegate_ = window_delegate; + } + + // Changes whether we display a throbber or the current favicon and + // forces a repaint of the titlebar. + void SetShowThrobber(bool show_throbber); + + // Overridden from ChromeViews::CustomFrameWindow::NonClientView: + virtual void Init(ChromeViews::ClientView* client_view); + virtual gfx::Rect CalculateClientAreaBounds(int width, int height) const; + virtual gfx::Size CalculateWindowSizeForClientSize(int width, + int height) const; + virtual CPoint GetSystemMenuPoint() const; + virtual int HitTest(const gfx::Point& point); + virtual void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask); + virtual void EnableClose(bool enable); + + // Overridden from ChromeViews::View: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Overridden from ChromeViews::BaseButton::ButtonListener: + virtual void ButtonPressed(ChromeViews::BaseButton* sender); + + // Overridden from LocationBarView::Delegate: + virtual TabContents* GetTabContents(); + virtual void OnInputInProgress(bool in_progress); + + // Overridden from Task: + virtual void Run(); + + // Updates the current throbber animation frame; called from the + // overloaded Run() and from SetShowThrobber(). + void UpdateThrobber(); + + // Whether we should display the throbber instead of the favicon. + bool should_show_throbber() const { + return show_throbber_ && current_throbber_frame_ != -1; + } + + // Paints different parts of the window to the incoming canvas. + void PaintFrameBorder(ChromeCanvas* canvas); + void PaintTitleBar(ChromeCanvas* canvas); + void PaintThrobber(ChromeCanvas* canvas); + void PaintFavicon(ChromeCanvas* canvas); + void PaintWindowTitle(ChromeCanvas* canvas); + + void UpdateLocationBar(); + bool ShouldDisplayURLField() const; + + // The View that provides the background for the window, and optionally + // dialog buttons. Note: the non-client view does _not_ own this view, the + // container does. + ChromeViews::ClientView* client_view_; + + ConstrainedWindowImpl* container_; + ChromeViews::WindowDelegate* window_delegate_; + + scoped_ptr<WindowResources> resources_; + + gfx::Rect title_bounds_; + gfx::Rect icon_bounds_; + gfx::Rect client_bounds_; + + ChromeViews::Button* close_button_; + + LocationBarView* location_bar_; + + // Specialization of ToolbarModel to obtain selected NavigationController for + // a constrained TabContents. + class ConstrainedWindowToolbarModel : public ToolbarModel { + public: + ConstrainedWindowToolbarModel(ConstrainedWindowImpl* constrained_window) + : constrained_window_(constrained_window) { + } + ~ConstrainedWindowToolbarModel() { } + + protected: + virtual NavigationController* GetNavigationController() { + TabContents* tab = constrained_window_->constrained_contents(); + return tab ? tab->controller() : NULL; + } + + private: + ConstrainedWindowImpl* constrained_window_; + + DISALLOW_EVIL_CONSTRUCTORS(ConstrainedWindowToolbarModel); + }; + + // The model used for the states of the location bar. + ConstrainedWindowToolbarModel toolbar_model_; + + // Whether we should display the animated throbber instead of the + // favicon. + bool show_throbber_; + + // The timer used to update frames for the throbber. + scoped_ptr<Timer> throbber_animation_timer_; + + // The current index into the throbber image strip. + int current_throbber_frame_; + + static void InitClass(); + + // The default favicon we render when the page has none. + static SkBitmap default_favicon_; + + // The throbber to display while a constrained window is loading. + static SkBitmap throbber_frames_; + + // The number of animation frames in throbber_frames_. + static int throbber_frame_count_; + + DISALLOW_EVIL_CONSTRUCTORS(ConstrainedWindowNonClientView); +}; + +SkBitmap ConstrainedWindowNonClientView::default_favicon_; +SkBitmap ConstrainedWindowNonClientView::throbber_frames_; +int ConstrainedWindowNonClientView::throbber_frame_count_ = -1; +static const int kWindowIconLeftOffset = 5; +static const int kWindowIconTopOffset = 5; +static const int kWindowControlsTopOffset = 1; +static const int kWindowControlsRightOffset = 4; +static const int kTitleTopOffset = 6; +static const int kWindowIconTitleSpacing = 3; +static const int kTitleBottomSpacing = 5; +static const int kNoTitleTopSpacing = 8; +static const int kResizeAreaSize = 5; +static const int kResizeAreaNorthSize = 3; +static const int kResizeAreaCornerSize = 16; +static const int kWindowHorizontalBorderSize = 5; +static const int kWindowVerticalBorderSize = 5; +static const int kWindowIconSize = 16; + +// How much wider or shorter the location bar is relative to the client area. +static const int kLocationBarOffset = 2; +// Spacing between the location bar and the content area. +static const int kLocationBarSpacing = 1; + +static const SkColor kContentsBorderShadow = SkColorSetARGB(51, 0, 0, 0); +static const SkColor kContentsBorderColor = SkColorSetRGB(219, 235, 255); + +static const int kThrobberFrameTimeMs = 30; + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, public: + +ConstrainedWindowNonClientView::ConstrainedWindowNonClientView( + ConstrainedWindowImpl* container, TabContents* owner) + : container_(container), + window_delegate_(NULL), + close_button_(new ChromeViews::Button), + location_bar_(NULL), + show_throbber_(false), + current_throbber_frame_(-1), + toolbar_model_(container) { + InitClass(); + if (owner->profile()->IsOffTheRecord()) { + resources_.reset(new OTRWindowResources); + } else { + if (win_util::ShouldUseVistaFrame()) { + resources_.reset(new VistaWindowResources); + } else { + resources_.reset(new XPWindowResources); + } + } + + close_button_->SetImage(ChromeViews::Button::BS_NORMAL, + resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON)); + close_button_->SetImage(ChromeViews::Button::BS_HOT, + resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_H)); + close_button_->SetImage(ChromeViews::Button::BS_PUSHED, + resources_->GetPartBitmap(FRAME_CLOSE_BUTTON_ICON_P)); + close_button_->SetImageAlignment(ChromeViews::Button::ALIGN_CENTER, + ChromeViews::Button::ALIGN_MIDDLE); + close_button_->SetListener(this, 0); + AddChildView(close_button_); + + throbber_animation_timer_.reset( + new Timer(kThrobberFrameTimeMs, this, true)); + + // Note: we don't need for a controller because no input event will be ever + // processed from a constrained window. + location_bar_ = new LocationBarView(owner->profile(), + NULL, + &toolbar_model_, + this, + true); + AddChildView(location_bar_); +} + +ConstrainedWindowNonClientView::~ConstrainedWindowNonClientView() { + MessageLoop::current()->timer_manager()->StopTimer( + throbber_animation_timer_.get()); +} + +void ConstrainedWindowNonClientView::UpdateLocationBar() { + if (ShouldDisplayURLField()) { + std::wstring url_spec; + TabContents* tab = container_->constrained_contents(); + url_spec = gfx::ElideUrl(tab->GetURL(), + ChromeFont(), + 0, + tab->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)); + std::wstring ev_text, ev_tooltip_text; + tab->GetSSLEVText(&ev_text, &ev_tooltip_text), + location_bar_->Update(NULL); + } +} + +bool ConstrainedWindowNonClientView::ShouldDisplayURLField() const { + // If the dialog is not fully initialized, default to showing the URL field. + if (!container_ || !container_->owner() || !container_->owner()->delegate()) + return true; + + return !container_->is_dialog() && + container_->owner()->delegate()->ShouldDisplayURLField(); +} + +int ConstrainedWindowNonClientView::CalculateTitlebarHeight() const { + int height; + if (window_delegate_ && window_delegate_->ShouldShowWindowTitle()) { + height = kTitleTopOffset + resources_->GetTitleFont().height() + + kTitleBottomSpacing; + } else { + height = kNoTitleTopSpacing; + } + + return height; +} + +int ConstrainedWindowNonClientView::CalculateNonClientHeight( + bool with_url_field) const { + int r = CalculateTitlebarHeight(); + + if (with_url_field) { + CSize s; + location_bar_->GetPreferredSize(&s); + r += s.cy; + } + return r; +} + +gfx::Rect ConstrainedWindowNonClientView::CalculateWindowBoundsForClientBounds( + const gfx::Rect& client_bounds, + bool with_url_field) const { + int non_client_height = CalculateNonClientHeight(with_url_field); + gfx::Rect window_bounds = client_bounds; + window_bounds.set_width( + window_bounds.width() + 2 * kWindowHorizontalBorderSize); + window_bounds.set_height( + window_bounds.height() + non_client_height + kWindowVerticalBorderSize); + window_bounds.set_x( + std::max(0, window_bounds.x() - kWindowHorizontalBorderSize)); + window_bounds.set_y(std::max(0, window_bounds.y() - non_client_height)); + return window_bounds; +} + +void ConstrainedWindowNonClientView::UpdateWindowIcon() { + SchedulePaint(icon_bounds_.ToRECT(), false); +} + +void ConstrainedWindowNonClientView::UpdateWindowTitle() { + SchedulePaint(title_bounds_.ToRECT(), false); + UpdateLocationBar(); +} + +void ConstrainedWindowNonClientView::SetShowThrobber(bool show_throbber) { + show_throbber_ = show_throbber; + + TimerManager* tm = MessageLoop::current()->timer_manager(); + Timer* timer = throbber_animation_timer_.get(); + if (show_throbber) { + if (!tm->IsTimerRunning(timer)) + tm->ResetTimer(timer); + } else { + if (tm->IsTimerRunning(timer)) { + UpdateThrobber(); + tm->StopTimer(timer); + } + } +} + +void ConstrainedWindowNonClientView::Run() { + UpdateThrobber(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, +// ChromeViews::CustomFrameWindow::NonClientView implementation: + +void ConstrainedWindowNonClientView::Init( + ChromeViews::ClientView* client_view) { + client_view_ = client_view; + AddChildView(client_view_); +} + +gfx::Rect ConstrainedWindowNonClientView::CalculateClientAreaBounds( + int width, + int height) const { + int non_client_height = CalculateNonClientHeight(ShouldDisplayURLField()); + return gfx::Rect(kWindowHorizontalBorderSize, non_client_height, + std::max(0, width - (2 * kWindowHorizontalBorderSize)), + std::max(0, height - non_client_height - kWindowVerticalBorderSize)); +} + +gfx::Size ConstrainedWindowNonClientView::CalculateWindowSizeForClientSize( + int width, + int height) const { + // This is only used for truly constrained windows, which does not include + // popups generated from a user gesture since those are detached immediately. + gfx::Rect window_bounds = + CalculateWindowBoundsForClientBounds(gfx::Rect(0, 0, width, height), + ShouldDisplayURLField()); + return window_bounds.size(); +} + +CPoint ConstrainedWindowNonClientView::GetSystemMenuPoint() const { + CPoint system_menu_point(icon_bounds_.x(), icon_bounds_.bottom()); + MapWindowPoints(container_->GetHWND(), HWND_DESKTOP, &system_menu_point, 1); + return system_menu_point; +} + +int ConstrainedWindowNonClientView::HitTest(const gfx::Point& point) { + CRect bounds; + CPoint test_point = point.ToPOINT(); + + // First see if it's within the grow box area, since that overlaps the client + // bounds. + if (client_view_->PointIsInSizeBox(point)) + return HTBOTTOMRIGHT; + + // Then see if it's within the client area. + if (client_view_) { + client_view_->GetBounds(&bounds); + if (bounds.PtInRect(test_point)) + return HTCLIENT; + } + + // Then see if the point is within any of the window controls. + close_button_->GetBounds(&bounds); + if (bounds.PtInRect(test_point)) + return HTCLOSE; + bounds = icon_bounds_.ToRECT(); + if (bounds.PtInRect(test_point)) + return HTSYSMENU; + + // Then see if the point is within the resize boundaries. + int width = GetWidth(); + int height = GetHeight(); + int component = HTNOWHERE; + if (point.x() < kResizeAreaSize) { + if (point.y() < kResizeAreaCornerSize) { + component = HTTOPLEFT; + } else if (point.y() >= (height - kResizeAreaCornerSize)) { + component = HTBOTTOMLEFT; + } else { + component = HTLEFT; + } + } else if (point.x() < kResizeAreaCornerSize) { + if (point.y() < kResizeAreaNorthSize) { + component = HTTOPLEFT; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOMLEFT; + } + } else if (point.x() >= (width - kResizeAreaSize)) { + if (point.y() < kResizeAreaCornerSize) { + component = HTTOPRIGHT; + } else if (point.y() >= (height - kResizeAreaCornerSize)) { + component = HTBOTTOMRIGHT; + } else if (point.x() >= (width - kResizeAreaSize)) { + component = HTRIGHT; + } + } else if (point.x() >= (width - kResizeAreaCornerSize)) { + if (point.y() < kResizeAreaNorthSize) { + component = HTTOPRIGHT; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOMRIGHT; + } + } else if (point.y() < kResizeAreaNorthSize) { + component = HTTOP; + } else if (point.y() >= (height - kResizeAreaSize)) { + component = HTBOTTOM; + } + + // If the window can't be resized, there are no resize boundaries, just + // window borders. + if (component != HTNOWHERE) { + if (window_delegate_ && !window_delegate_->CanResize()) { + return HTBORDER; + } + return component; + } + + // Finally fall back to the caption. + GetBounds(&bounds); + if (bounds.PtInRect(test_point)) + return HTCAPTION; + // The point is outside the window's bounds. + return HTNOWHERE; +} + +void ConstrainedWindowNonClientView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + DCHECK(window_mask); + + // Redefine the window visible region for the new size. + window_mask->moveTo(0, 3); + window_mask->lineTo(1, 2); + window_mask->lineTo(1, 1); + window_mask->lineTo(2, 1); + window_mask->lineTo(3, 0); + + window_mask->lineTo(SkIntToScalar(size.width() - 3), 0); + window_mask->lineTo(SkIntToScalar(size.width() - 2), 1); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 1); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 2); + window_mask->lineTo(SkIntToScalar(size.width()), 3); + + window_mask->lineTo(SkIntToScalar(size.width()), + SkIntToScalar(size.height())); + window_mask->lineTo(0, SkIntToScalar(size.height())); + window_mask->close(); +} + +void ConstrainedWindowNonClientView::EnableClose(bool enable) { + close_button_->SetEnabled(enable); +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, ChromeViews::View implementation: + +void ConstrainedWindowNonClientView::Paint(ChromeCanvas* canvas) { + PaintFrameBorder(canvas); + PaintTitleBar(canvas); +} + +void ConstrainedWindowNonClientView::Layout() { + bool should_display_url_field = false; + if (location_bar_) { + should_display_url_field = ShouldDisplayURLField(); + location_bar_->SetVisible(should_display_url_field); + } + + int location_bar_height = 0; + CSize ps; + if (should_display_url_field) { + location_bar_->GetPreferredSize(&ps); + location_bar_height = ps.cy; + } + + close_button_->GetPreferredSize(&ps); + close_button_->SetBounds(GetWidth() - ps.cx - kWindowControlsRightOffset, + kWindowControlsTopOffset, ps.cx, ps.cy); + + int titlebar_height = CalculateTitlebarHeight(); + if (window_delegate_) { + int icon_y = (titlebar_height - kWindowIconSize) / 2; + icon_bounds_.SetRect(kWindowIconLeftOffset, icon_y, 0, 0); + if (window_delegate_->ShouldShowWindowIcon()) { + icon_bounds_.set_width(kWindowIconSize); + icon_bounds_.set_height(kWindowIconSize); + } + + if (window_delegate_->ShouldShowWindowTitle()) { + int spacing = window_delegate_->ShouldShowWindowIcon() ? + kWindowIconTitleSpacing : 0; + int title_right = close_button_->GetX() - spacing; + int title_left = icon_bounds_.right() + spacing; + title_bounds_.SetRect(title_left, kTitleTopOffset, + title_right - title_left, + resources_->GetTitleFont().height()); + } + } + + client_bounds_ = CalculateClientAreaBounds(GetWidth(), GetHeight()); + if (should_display_url_field) { + location_bar_->SetBounds(client_bounds_.x() - kLocationBarOffset, + client_bounds_.y() - location_bar_height - + kLocationBarSpacing, + client_bounds_.width() + kLocationBarOffset * 2, + location_bar_height); + location_bar_->Layout(); + } + if (client_view_) + client_view_->SetBounds(client_bounds_.ToRECT()); +} + +void ConstrainedWindowNonClientView::GetPreferredSize(CSize* out) { + DCHECK(out); + if (client_view_) { + client_view_->GetPreferredSize(out); + out->cx += 2 * kWindowHorizontalBorderSize; + out->cy += CalculateNonClientHeight(ShouldDisplayURLField()) + + kWindowVerticalBorderSize; + } +} + +void ConstrainedWindowNonClientView::ViewHierarchyChanged(bool is_add, + View *parent, + View *child) { + if (is_add && location_bar_ && GetViewContainer() && + !(location_bar_->IsInitialized())) { + location_bar_->Init(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, ChromeViews::BaseButton::Button +// implementation: + +void ConstrainedWindowNonClientView::ButtonPressed( + ChromeViews::BaseButton* sender) { + if (sender == close_button_) + container_->ExecuteSystemMenuCommand(SC_CLOSE); +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, LocationBarView::Delegate +// implementation: +TabContents* ConstrainedWindowNonClientView::GetTabContents() { + return container_->owner(); +} + +void ConstrainedWindowNonClientView::OnInputInProgress(bool in_progress) { +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowNonClientView, private: + +void ConstrainedWindowNonClientView::UpdateThrobber() { + if (show_throbber_) + current_throbber_frame_ = ++current_throbber_frame_ % throbber_frame_count_; + else + current_throbber_frame_ = -1; + + SchedulePaint(); +} + +void ConstrainedWindowNonClientView::PaintFrameBorder(ChromeCanvas* canvas) { + int width = GetWidth(); + int height = GetHeight(); + + SkBitmap* top_left_corner = resources_->GetPartBitmap(FRAME_TOP_LEFT_CORNER); + SkBitmap* top_right_corner = + resources_->GetPartBitmap(FRAME_TOP_RIGHT_CORNER); + SkBitmap* top_edge = resources_->GetPartBitmap(FRAME_TOP_CENTER); + SkBitmap* right_edge = resources_->GetPartBitmap(FRAME_RIGHT_SIDE); + SkBitmap* left_edge = resources_->GetPartBitmap(FRAME_LEFT_SIDE); + SkBitmap* bottom_left_corner = + resources_->GetPartBitmap(FRAME_BOTTOM_LEFT_CORNER); + SkBitmap* bottom_right_corner = + resources_->GetPartBitmap(FRAME_BOTTOM_RIGHT_CORNER); + SkBitmap* bottom_edge = resources_->GetPartBitmap(FRAME_BOTTOM_CENTER); + + // Top. + canvas->DrawBitmapInt(*top_left_corner, 0, 0); + canvas->TileImageInt(*top_edge, top_left_corner->width(), 0, + width - top_right_corner->width(), top_edge->height()); + canvas->DrawBitmapInt( + *top_right_corner, width - top_right_corner->width(), 0); + + // Right. + int top_stack_height = top_right_corner->height(); + canvas->TileImageInt(*right_edge, width - right_edge->width(), + top_stack_height, right_edge->width(), + height - top_stack_height - + bottom_right_corner->height()); + + // Bottom. + canvas->DrawBitmapInt(*bottom_right_corner, + width - bottom_right_corner->width(), + height - bottom_right_corner->height()); + canvas->TileImageInt(*bottom_edge, bottom_left_corner->width(), + height - bottom_edge->height(), + width - bottom_left_corner->width() - + bottom_right_corner->width(), + bottom_edge->height()); + canvas->DrawBitmapInt(*bottom_left_corner, 0, + height - bottom_left_corner->height()); + + // Left. + top_stack_height = top_left_corner->height(); + canvas->TileImageInt(*left_edge, 0, top_stack_height, left_edge->width(), + height - top_stack_height - + bottom_left_corner->height()); + + // Contents Border. + gfx::Rect border_bounds = client_bounds_; + border_bounds.Inset(-2, -2); + canvas->FillRectInt(kContentsBorderShadow, border_bounds.x(), + border_bounds.y(), border_bounds.width(), + border_bounds.height()); + + border_bounds.Inset(1, 1); + canvas->FillRectInt(kContentsBorderColor, border_bounds.x(), + border_bounds.y(), border_bounds.width(), + border_bounds.height()); +} + +void ConstrainedWindowNonClientView::PaintTitleBar(ChromeCanvas* canvas) { + if (!window_delegate_) + return; + + if (window_delegate_->ShouldShowWindowIcon()) { + if (should_show_throbber()) + PaintThrobber(canvas); + else + PaintFavicon(canvas); + } + if (window_delegate_->ShouldShowWindowTitle()) { + PaintWindowTitle(canvas); + } +} + +void ConstrainedWindowNonClientView::PaintThrobber(ChromeCanvas* canvas) { + int image_size = throbber_frames_.height(); + int image_offset = current_throbber_frame_ * image_size; + canvas->DrawBitmapInt(throbber_frames_, + image_offset, 0, image_size, image_size, + icon_bounds_.x(), icon_bounds_.y(), + image_size, image_size, + false); +} + +void ConstrainedWindowNonClientView::PaintFavicon(ChromeCanvas* canvas) { + SkBitmap window_icon = window_delegate_->GetWindowIcon(); + if (window_icon.isNull()) + window_icon = default_favicon_; + canvas->DrawBitmapInt(window_icon, 0, 0, window_icon.width(), + window_icon.height(), icon_bounds_.x(), + icon_bounds_.y(), icon_bounds_.width(), + icon_bounds_.height(), false); +} + +void ConstrainedWindowNonClientView::PaintWindowTitle(ChromeCanvas* canvas) { + canvas->DrawStringInt(container_->GetWindowTitle(), + resources_->GetTitleFont(), + resources_->GetTitleColor(), title_bounds_.x(), + title_bounds_.y(), title_bounds_.width(), + title_bounds_.height()); +} + +// static +void ConstrainedWindowNonClientView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + default_favicon_ = *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); + + throbber_frames_ = *rb.GetBitmapNamed(IDR_THROBBER); + DCHECK(throbber_frames_.width() % throbber_frames_.height() == 0); + throbber_frame_count_ = + throbber_frames_.width() / throbber_frames_.height(); + + initialized = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedTabContentsWindowDelegate + +class ConstrainedTabContentsWindowDelegate + : public ChromeViews::WindowDelegate { + public: + explicit ConstrainedTabContentsWindowDelegate( + ConstrainedWindowImpl* window) + : window_(window) { + } + + // ChromeViews::WindowDelegate implementation: + virtual bool CanResize() const { + return true; + } + virtual std::wstring GetWindowTitle() const { + TabContents* constrained_contents = window_->constrained_contents(); + if (constrained_contents) + return constrained_contents->GetTitle(); + + return std::wstring(); + } + virtual bool ShouldShowWindowIcon() const { + return true; + } + virtual SkBitmap GetWindowIcon() { + TabContents* constrained_contents = window_->constrained_contents(); + if (constrained_contents) + return constrained_contents->GetFavIcon(); + + return SkBitmap(); + } + + private: + ConstrainedWindowImpl* window_; + + DISALLOW_EVIL_CONSTRUCTORS(ConstrainedTabContentsWindowDelegate); +}; + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl, public: + +// The space (in pixels) between minimized pop-ups stacked horizontally and +// vertically. +static const int kPopupRepositionOffset = 5; +static const int kConstrainedWindowEdgePadding = 10; + +ConstrainedWindowImpl::ConstrainedWindowImpl(TabContents* owner) + : CustomFrameWindow(new ConstrainedWindowNonClientView(this, owner)), + owner_(owner), + constrained_contents_(NULL), + focus_restoration_disabled_(false), + is_dialog_(false), + titlebar_visibility_(0.0), + contents_container_(NULL) { + set_window_style(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | + WS_THICKFRAME | WS_SYSMENU); + set_focus_on_creation(false); +} + +ConstrainedWindowImpl::~ConstrainedWindowImpl() { +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl, ConstrainedWindow implementation: + +ConstrainedWindowNonClientView* ConstrainedWindowImpl::non_client_view() { + return static_cast<ConstrainedWindowNonClientView*>(non_client_view_); +} + +void ConstrainedWindowImpl::ActivateConstrainedWindow() { + if (CanDetach()) { + // Detachable pop-ups are torn out as soon as the window is activated. + Detach(); + return; + } + + // Other pop-ups are simply moved to the front of the z-order. + SetWindowPos(HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + // Store the focus of our parent focus manager so we can restore it when we + // close. + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(GetHWND()); + DCHECK(focus_manager); + focus_manager = focus_manager->GetParentFocusManager(); + if (focus_manager) { + // We could not have a parent focus manager if the ConstrainedWindow is + // displayed in a tab that is not currently selected. + // TODO(jcampan): we should store the ConstrainedWindow active events in + // that case and replay them when the WebContents becomes selected. + focus_manager->StoreFocusedView(); + + if (constrained_contents_) { + // We contain another window, let's assume it knows how to process the + // focus and let's focus it. + // TODO(jcampan): so far this case is the WebContents case. We need to + // better find whether the inner window should get focus. + ::SetFocus(constrained_contents_->GetContainerHWND()); + } else { + // Give our window the focus so we get keyboard messages. + ::SetFocus(GetHWND()); + } + } +} + +void ConstrainedWindowImpl::CloseConstrainedWindow() { + // Broadcast to all observers of NOTIFY_CWINDOW_CLOSED. + // One example of such an observer is AutomationCWindowTracker in the + // automation component. + NotificationService::current()->Notify(NOTIFY_CWINDOW_CLOSED, + Source<ConstrainedWindow>(this), + NotificationService::NoDetails()); + + Close(); +} + +void ConstrainedWindowImpl::ResizeConstrainedWindow(int width, int height) { + gfx::Size window_size = + non_client_view_->CalculateWindowSizeForClientSize(width, height); + ::SetWindowPos(GetHWND(), NULL, 0, 0, window_size.width(), + window_size.height(), + SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); +} + +void ConstrainedWindowImpl::RepositionConstrainedWindowTo( + const gfx::Point& anchor_point) { + anchor_point_ = anchor_point; + ResizeConstrainedTitlebar(); +} + +bool ConstrainedWindowImpl::IsSuppressedConstrainedWindow() const { + return !is_dialog_; +} + +void ConstrainedWindowImpl::WasHidden() { + if (constrained_contents_) + constrained_contents_->WasHidden(); +} + +void ConstrainedWindowImpl::DidBecomeSelected() { + if (constrained_contents_) + constrained_contents_->DidBecomeSelected(); +} + +std::wstring ConstrainedWindowImpl::GetWindowTitle() const { + // TODO(beng): (http://b/1085485) Need to decide if we want to append the app + // name to all constrained windows or just web renderers. + std::wstring page_title; + if (window_delegate()) + page_title = window_delegate()->GetWindowTitle(); + + std::wstring display_title; + if (page_title.empty()) { + display_title = l10n_util::GetString(IDS_PRODUCT_NAME); + } else { + display_title = l10n_util::GetStringF(IDS_BROWSER_WINDOW_TITLE_FORMAT, + page_title); + } + return display_title; +} + +void ConstrainedWindowImpl::UpdateWindowTitle() { + UpdateUI(TabContents::INVALIDATE_TITLE); +} + +const gfx::Rect& ConstrainedWindowImpl::GetCurrentBounds() const { + return current_bounds_; +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl, TabContentsDelegate implementation: + +void ConstrainedWindowImpl::NavigationStateChanged( + const TabContents* source, + unsigned int changed_flags) { + UpdateUI(changed_flags); +} + +void ConstrainedWindowImpl::ReplaceContents(TabContents* source, + TabContents* new_contents) { + source->set_delegate(NULL); + + constrained_contents_ = new_contents; + constrained_contents_->set_delegate(this); + UpdateUI(TabContents::INVALIDATE_EVERYTHING); +} + +void ConstrainedWindowImpl::AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + // Pass this to the delegate, since we can't open new tabs in the Constrained + // Window, they are sent up to the browser to open as new tabs. + owner_->AddNewContents( + this, new_contents, disposition, initial_pos, user_gesture); +} + +void ConstrainedWindowImpl::ActivateContents(TabContents* contents) { + // Ask the delegate's (which is a TabContents) own TabContentsDelegate to + // activate itself... + owner_->delegate()->ActivateContents(owner_); + + // Set as the foreground constrained window. + ActivateConstrainedWindow(); +} + +void ConstrainedWindowImpl::OpenURLFromTab(TabContents* source, + const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + owner_->OpenURL(this, url, disposition, transition); +} + +void ConstrainedWindowImpl::LoadingStateChanged(TabContents* source) { + // TODO(beng): (http://b/1085543) Implement a throbber for the Constrained + // Window. + UpdateUI(TabContents::INVALIDATE_EVERYTHING); + non_client_view()->SetShowThrobber(source->is_loading()); +} + +void ConstrainedWindowImpl::NavigateToPage(TabContents* source, + const GURL& url, + PageTransition::Type transition) { + UpdateUI(TabContents::INVALIDATE_EVERYTHING); +} + +void ConstrainedWindowImpl::SetTitlebarVisibilityPercentage(double percentage) { + titlebar_visibility_ = percentage; + ResizeConstrainedTitlebar(); +} + +void ConstrainedWindowImpl::StartSuppressedAnimation() { + animation_.reset(new ConstrainedWindowAnimation(this)); + animation_->Start(); +} + +void ConstrainedWindowImpl::CloseContents(TabContents* source) { + Close(); +} + +void ConstrainedWindowImpl::MoveContents(TabContents* source, + const gfx::Rect& pos) { + if (!IsSuppressedConstrainedWindow()) + SetWindowBounds(pos); +} + +bool ConstrainedWindowImpl::IsPopup(TabContents* source) { + return true; +} + +TabContents* ConstrainedWindowImpl::GetConstrainingContents( + TabContents* source) { + return owner_; +} + +void ConstrainedWindowImpl::ToolbarSizeChanged(TabContents* source, + bool finished) { + // We don't control the layout of anything that could be animating, + // so do nothing. +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl, private: + +void ConstrainedWindowImpl::ResizeConstrainedTitlebar() { + DCHECK(constrained_contents_) + << "ResizeConstrainedTitlebar() is only valid for web popups"; + // If we represent a web popup and we were not opened as the result of a + // user gesture, we override the position specified in |initial_bounds| to + // place ourselves at the bottom right of the parent HWND. + CRect this_bounds; + GetClientRect(&this_bounds); + + // First determine the height of the title bar of a constrained window, so + // that we can offset by that much vertically if necessary... + int titlebar_height = non_client_view()->CalculateTitlebarHeight(); + + int visible_titlebar_pixels = + static_cast<int>(titlebar_height * titlebar_visibility_); + + int x = anchor_point_.x() - this_bounds.Width(); + int y = anchor_point_.y() - visible_titlebar_pixels; + SetWindowPos(NULL, x, y, this_bounds.Width(), visible_titlebar_pixels, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); +} + +void ConstrainedWindowImpl::InitAsDialog( + const gfx::Rect& initial_bounds, + ChromeViews::View* contents_view, + ChromeViews::WindowDelegate* window_delegate) { + is_dialog_ = true; + non_client_view()->set_window_delegate(window_delegate); + CustomFrameWindow::Init(owner_->GetContainerHWND(), initial_bounds, + contents_view, window_delegate); + ActivateConstrainedWindow(); +} + +void ConstrainedWindowImpl::InitWindowForContents( + TabContents* constrained_contents) { + constrained_contents_ = constrained_contents; + constrained_contents_->set_delegate(this); + contents_container_ = new ChromeViews::HWNDView; + contents_window_delegate_.reset( + new ConstrainedTabContentsWindowDelegate(this)); + + non_client_view()->set_window_delegate(contents_window_delegate_.get()); +} + +void ConstrainedWindowImpl::InitSizeForContents( + const gfx::Rect& initial_bounds) { + CustomFrameWindow::Init(owner_->GetContainerHWND(), initial_bounds, + contents_container_, contents_window_delegate_.get()); + contents_container_->Attach(constrained_contents_->GetContainerHWND()); + + constrained_contents_->SizeContents( + gfx::Size(contents_container_->GetWidth(), + contents_container_->GetHeight())); + current_bounds_ = initial_bounds; + + // Note that this is HWND_TOP, not HWND_TOPMOST... this is important + // because otherwise the window will not be visible on top of the + // RenderWidgetHostView! + win_util::SetChildBounds(GetHWND(), GetParent(), HWND_TOP, initial_bounds, + kConstrainedWindowEdgePadding, 0); +} + +bool ConstrainedWindowImpl::CanDetach() const { + // Constrained TabContentses can be detached, dialog boxes can't. + return constrained_contents_ ? true : false; +} + +void ConstrainedWindowImpl::Detach() { + DCHECK(CanDetach()); + // Tell the container not to restore focus to whatever view was focused last, + // since this will interfere with the new window activation in the case where + // a constrained window is destroyed by being detached. + focus_restoration_disabled_ = true; + + // Detach the HWND immediately. + contents_container_->Detach(); + contents_container_ = NULL; + + // To try and create as seamless as possible a popup experience, web pop-ups + // are automatically detached when the user interacts with them. We can + // dial this back if we feel this is too much. + + // The detached contents "should" be re-parented by the delegate's + // DetachContents, but we clear the delegate pointing to us just in case. + constrained_contents_->set_delegate(NULL); + + CRect bounds; + ::GetWindowRect(constrained_contents_->GetContainerHWND(), &bounds); + CPoint cursor_pos; + ::GetCursorPos(&cursor_pos); + gfx::Point screen_point(cursor_pos.x, cursor_pos.y); + int frame_component = static_cast<int>(OnNCHitTest(screen_point.ToPOINT())); + owner_->DetachContents(this, constrained_contents_, gfx::Rect(bounds), + screen_point, frame_component); + constrained_contents_ = NULL; + Close(); +} + +void ConstrainedWindowImpl::SetWindowBounds(const gfx::Rect& bounds) { + // Note: SetChildBounds ensures that the constrained window is constrained + // to the bounds of its parent, however there remains a bug where the + // window is positioned incorrectly when the outer window is opened on + // a monitor that has negative coords (e.g. secondary monitor to left + // of primary, see http://b/issue?id=967905.) + gfx::Size window_size = non_client_view()->CalculateWindowSizeForClientSize( + bounds.width(), bounds.height()); + + current_bounds_ = bounds; + current_bounds_.set_width(window_size.width()); + current_bounds_.set_height(window_size.height()); + win_util::SetChildBounds(GetHWND(), GetParent(), NULL, current_bounds_, + kConstrainedWindowEdgePadding, 0); +} + +void ConstrainedWindowImpl::UpdateUI(unsigned int changed_flags) { + if (changed_flags & TabContents::INVALIDATE_FAVICON) + non_client_view()->UpdateWindowIcon(); + if (changed_flags & TabContents::INVALIDATE_TITLE) + non_client_view()->UpdateWindowTitle(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl, ChromeViews::HWNDViewContainer overrides: + +void ConstrainedWindowImpl::OnDestroy() { + // We do this here, rather than |Close|, since the window may be destroyed in + // a way other than by some other component calling Close, e.g. by the native + // window hierarchy closing. We are guaranteed to receive a WM_DESTROY + // message regardless of how the window is closed. + // Note that when we get this message, the focus manager of the + // ConstrainedWindow has already been destroyed (by the processing of + // WM_DESTROY in FocusManager). So the FocusManager we retrieve here is the + // parent one (the one from the top window). + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(GetHWND()); + if (focus_manager) { + // We may not have a focus manager if: + // - we are hidden when closed (the TabContent would be detached). + // - the tab has been closed and we are closed as a result. + // TODO(jcampan): when hidden, we should modify the stored focus of the tab + // so when it becomes visible again we retrieve the focus appropriately. + if (!focus_restoration_disabled_) + focus_manager->RestoreFocusedView(); + } + + // If we have a child TabContents, we need to unhook it here so that it is + // not automatically WM_DESTROYed by virtue of the fact that it is part of + // our Window hierarchy. Rather, it needs to be destroyed just like top level + // TabContentses are: from OnMsgCloseACK in RenderWidgetHost. So we hide the + // TabContents and sever the parent relationship now. Note the GetParent + // check so that we don't hide and re-parent TabContentses that have been + // detached and re-attached into a new top level browser window via a user + // drag action. + if (constrained_contents_ && + ::GetParent(constrained_contents_->GetContainerHWND()) == GetHWND()) { + ::ShowWindow(constrained_contents_->GetContainerHWND(), SW_HIDE); + ::SetParent(constrained_contents_->GetContainerHWND(), NULL); + } + + // Make sure we call super so that it can do its cleanup. + Window::OnDestroy(); +} + +void ConstrainedWindowImpl::OnFinalMessage(HWND window) { + // Tell our constraining TabContents that we've gone so it can update its + // list. + owner_->WillClose(this); + if (constrained_contents_) { + constrained_contents_->CloseContents(); + constrained_contents_ = NULL; + } + + HWNDViewContainer::OnFinalMessage(window); +} + +void ConstrainedWindowImpl::OnGetMinMaxInfo(LPMINMAXINFO mm_info) { + // Override this function to set the maximize area as the client area of the + // containing window. + CRect parent_rect; + ::GetClientRect(GetParent(), &parent_rect); + + mm_info->ptMaxSize.x = parent_rect.Width(); + mm_info->ptMaxSize.y = parent_rect.Height(); + mm_info->ptMaxPosition.x = parent_rect.left; + mm_info->ptMaxPosition.y = parent_rect.top; +} + +LRESULT ConstrainedWindowImpl::OnMouseActivate(HWND window, + UINT hittest_code, + UINT message) { + // We need to store this value before we call ActivateConstrainedWindow() + // since the window may be detached and so this function will return false + // afterwards. + bool can_detach = CanDetach(); + + // We only detach the window if the user clicked on the title bar. That + // way, users can click inside the contents of legitimate popups obtained + // with a mouse gesture. + if (hittest_code == HTCAPTION) { + ActivateConstrainedWindow(); + } else { + // If the user did not click on the title bar, don't stop message + // propagation. + can_detach = false; + } + + // If the popup can be detached, then we tell the parent window not to + // activate since we will already have adjusted activation ourselves. We also + // do _not_ eat the event otherwise the user will have to click again to + // interact with the popup. + return can_detach ? MA_NOACTIVATEANDEAT : MA_ACTIVATE; +} + +void ConstrainedWindowImpl::OnWindowPosChanged(WINDOWPOS* window_pos) { + // If the window was moved or sized, tell the owner. + if (!(window_pos->flags & SWP_NOMOVE) || !(window_pos->flags & SWP_NOSIZE)) + owner_->DidMoveOrResize(this); + SetMsgHandled(FALSE); +} + +/////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindow, public: + +// static +void ConstrainedWindow::GenerateInitialBounds( + const gfx::Rect& initial_bounds, TabContents* parent, + gfx::Rect* window_bounds) { + // Calculate desired window bounds. Try to use the bounds of a + // non-maximized browser window; this matches other browsers' behavior. + // + // NOTE: The downside here is that, if we open multiple constrained popups, + // they'll all get the same window position, since WindowSizer uses the + // "last active browser window"'s bounds. Fixing this properly is hard, + // since we'd have to tell the WindowSizer about the window we're opening + // here, and figure out how the sizing memory and the clipping/offsetting + // behvaiors below interact. + std::wstring app_name; + + if (parent->delegate() && parent->delegate()->IsApplication() && + parent->AsWebContents() && parent->AsWebContents()->web_app()) { + app_name = parent->AsWebContents()->web_app()->name(); + } + bool maximized = false; + gfx::Rect empty_bounds; + WindowSizer::GetBrowserWindowBounds(app_name, empty_bounds, + window_bounds, &maximized); + if (initial_bounds.width() > 0) + window_bounds->set_width(initial_bounds.width()); + if (initial_bounds.height() > 0) + window_bounds->set_height(initial_bounds.height()); + + // Map desired window bounds from screen coordinates to our parent's + // coordinates. + CPoint window_origin(window_bounds->origin().ToPOINT()); + MapWindowPoints(HWND_DESKTOP, parent->GetContainerHWND(), &window_origin, + 1); + window_bounds->set_origin(gfx::Point(window_origin)); + + // Ensure some amount of the page is visible above and to the left of the + // popup, so it doesn't cover the whole content area (we use 30 px). + if (window_bounds->x() < 30) + window_bounds->set_x(30); + if (window_bounds->y() < 30) + window_bounds->set_y(30); + + // Clip the desired coordinates so they fit within the content area. + CRect parent_rect; + ::GetClientRect(parent->GetContainerHWND(), &parent_rect); + if (window_bounds->right() > parent_rect.right) + window_bounds->set_width(parent_rect.Width() - window_bounds->x()); + if (window_bounds->bottom() > parent_rect.bottom) + window_bounds->set_height(parent_rect.Height() - window_bounds->y()); + + // Don't let the window become too small (we use a 60x30 minimum size). + if (window_bounds->width() < 60) + window_bounds->set_width(60); + if (window_bounds->height() < 30) + window_bounds->set_height(30); +} + +// static +ConstrainedWindow* ConstrainedWindow::CreateConstrainedDialog( + TabContents* parent, + const gfx::Rect& initial_bounds, + ChromeViews::View* contents_view, + ChromeViews::WindowDelegate* window_delegate) { + ConstrainedWindowImpl* window = new ConstrainedWindowImpl(parent); + window->InitAsDialog(initial_bounds, contents_view, window_delegate); + return window; +} + +// static +ConstrainedWindow* ConstrainedWindow::CreateConstrainedPopup( + TabContents* parent, + const gfx::Rect& initial_bounds, + TabContents* constrained_contents) { + ConstrainedWindowImpl* window = + new ConstrainedWindowImpl(parent); + window->InitWindowForContents(constrained_contents); + + gfx::Rect window_bounds; + if (initial_bounds.width() == 0 || initial_bounds.height() == 0) { + GenerateInitialBounds(initial_bounds, parent, &window_bounds); + } else { + window_bounds = window->non_client_view()-> + CalculateWindowBoundsForClientBounds( + initial_bounds, + parent->delegate()->ShouldDisplayURLField()); + } + + window->InitSizeForContents(window_bounds); + + // This is a constrained popup window and thus we need to animate it in. + window->StartSuppressedAnimation(); + + return window; +} diff --git a/chrome/browser/views/constrained_window_impl.h b/chrome/browser/views/constrained_window_impl.h new file mode 100644 index 0000000..f3916ab --- /dev/null +++ b/chrome/browser/views/constrained_window_impl.h @@ -0,0 +1,214 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_CONSTRAINED_WINDOW_IMPL_H__ +#define CHROME_BROWSER_CONSTRAINED_WINDOW_IMPL_H__ + +#include "chrome/browser/constrained_window.h" +#include "chrome/browser/tab_contents_delegate.h" +#include "chrome/views/client_view.h" +#include "chrome/views/custom_frame_window.h" + +class ConstrainedTabContentsWindowDelegate; +class ConstrainedWindowAnimation; +class ConstrainedWindowNonClientView; + +/////////////////////////////////////////////////////////////////////////////// +// ConstrainedWindowImpl +// +// A ConstrainedWindow implementation that implements a Constrained Window as +// a child HWND with a custom window frame. +// +class ConstrainedWindowImpl : public ConstrainedWindow, + public ChromeViews::CustomFrameWindow, + public TabContentsDelegate { + public: + virtual ~ConstrainedWindowImpl(); + + // Returns the TabContents that constrains this Constrained Window. + TabContents* owner() const { return owner_; } + + TabContents* constrained_contents() const { return constrained_contents_; } + // Returns the non-client view inside this Constrained Window. + // NOTE: Defining the function body here would require pulling in the + // declarations of ConstrainedWindowNonClientView, as well as all the classes + // it depends on, from the .cc file; the benefit isn't worth it. + ConstrainedWindowNonClientView* non_client_view(); + + // Overridden from ConstrainedWindow: + virtual void CloseConstrainedWindow(); + virtual void ActivateConstrainedWindow(); + virtual void ResizeConstrainedWindow(int width, int height); + virtual void RepositionConstrainedWindowTo(const gfx::Point& anchor_point); + virtual bool IsSuppressedConstrainedWindow() const; + virtual void WasHidden(); + virtual void DidBecomeSelected(); + virtual std::wstring GetWindowTitle() const; + virtual void UpdateWindowTitle(); + virtual const gfx::Rect& GetCurrentBounds() const; + + // Overridden from TabContentsDelegate: + virtual void OpenURLFromTab(TabContents* source, + const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition); + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags); + virtual void ReplaceContents(TabContents* source, + TabContents* new_contents); + virtual void AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ActivateContents(TabContents* contents); + virtual void LoadingStateChanged(TabContents* source); + virtual void CloseContents(TabContents* source); + virtual void MoveContents(TabContents* source, const gfx::Rect& pos); + virtual bool IsPopup(TabContents* source); + virtual TabContents* GetConstrainingContents(TabContents* source); + virtual void ToolbarSizeChanged(TabContents* source, bool is_animating); + virtual void URLStarredChanged(TabContents* source, bool) {} + virtual void UpdateTargetURL(TabContents* source, const GURL& url) {} + virtual bool CanBlur() const { return false; } + + virtual void NavigateToPage(TabContents* source, const GURL& url, + PageTransition::Type transition); + + + bool is_dialog() { return is_dialog_; } + + // Changes the visibility of the titlebar. |percentage| is a real + // number ranged 0,1. + void SetTitlebarVisibilityPercentage(double percentage); + + // Starts a ConstrainedWindowAnimation to slide in the titlebar of + // this suppressed constrained popup window. + void StartSuppressedAnimation(); + + protected: + // Windows message handlers: + virtual void OnDestroy(); + virtual void OnFinalMessage(HWND window); + virtual void OnGetMinMaxInfo(LPMINMAXINFO mm_info); + virtual LRESULT OnMouseActivate(HWND window, UINT hittest_code, UINT message); + virtual void OnWindowPosChanged(WINDOWPOS* window_pos); + + private: + // Use the static factory methods on ConstrainedWindow to construct a + // ConstrainedWindow. + ConstrainedWindowImpl(TabContents* owner); + + friend class ConstrainedWindow; + + // Called after changing either the anchor point or titlebar + // visibility of a suppressed popup. This does the actual resizing. + // + // @see RepositionConstrainedWindowTo + // @see SetTitlebarVisibilityPercentage + void ResizeConstrainedTitlebar(); + + // Initialize the Constrained Window as a Constrained Dialog containing a + // ChromeViews::View client area. + void InitAsDialog(const gfx::Rect& initial_bounds, + ChromeViews::View* contents_view, + ChromeViews::WindowDelegate* window_delegate); + + // Builds the underlying HWND and window delegates for a newly + // created popup window. + // + // We have to split the initialization process for a popup window in + // two because we first need to initialize a proper window delegate + // so that when we query for desired size, we get accurate data. If + // we didn't do this, windows will initialize to being smaller then + // the desired content size plus room for browser chrome. + void InitWindowForContents(TabContents* constrained_contents); + + // Sets the initial bounds for a newly created popup window. + // + // This is the second part of the initialization process started + // with InitWindowForContents. For the parameter initial_bounds to + // have been calculated correctly, InitWindowForContents must have + // been run first. + void InitSizeForContents(const gfx::Rect& initial_bounds); + + // Returns true if the Constrained Window can be detached from its owner. + bool CanDetach() const; + + // Detach the Constrained TabContents from its owner. + void Detach(); + + // Updates the portions of the UI as specified in |changed_flags|. + void UpdateUI(unsigned int changed_flags); + + // Place and size the window, constraining to the bounds of the |owner_|. + void SetWindowBounds(const gfx::Rect& bounds); + + // The TabContents that owns and constrains this ConstrainedWindow. + TabContents* owner_; + + // The TabContents constrained by |owner_|. + TabContents* constrained_contents_; + + // True if focus should not be restored to whatever view was focused last + // when this window is destroyed. + bool focus_restoration_disabled_; + + // A default ChromeViews::WindowDelegate implementation for this window when + // a TabContents is being constrained. (For the Constrained Dialog case, the + // caller is required to provide the WindowDelegate). + scoped_ptr<ConstrainedTabContentsWindowDelegate> contents_window_delegate_; + + // We keep a reference on the HWNDView so we can properly detach the tab + // contents when detaching. + ChromeViews::HWNDView* contents_container_; + + // true if this window is really a constrained dialog. This is set by + // InitAsDialog(). + bool is_dialog_; + + // Current "anchor point", the lower right point at which we render + // the constrained title bar. + gfx::Point anchor_point_; + + // The 0,1 percentage representing what amount of a titlebar of a + // suppressed popup window should be visible. Used to animate those + // titlebars in. + double titlebar_visibility_; + + // The animation class which animates constrained windows onto the page. + scoped_ptr<ConstrainedWindowAnimation> animation_; + + // Current display rectangle (relative to owner_'s visible area). + gfx::Rect current_bounds_; + + DISALLOW_EVIL_CONSTRUCTORS(ConstrainedWindowImpl); +}; + +#endif // #ifndef CHROME_BROWSER_CONSTRAINED_WINDOW_IMPL_H__ diff --git a/chrome/browser/views/constrained_window_impl_interactive_uitest.cc b/chrome/browser/views/constrained_window_impl_interactive_uitest.cc new file mode 100644 index 0000000..b06efd3 --- /dev/null +++ b/chrome/browser/views/constrained_window_impl_interactive_uitest.cc @@ -0,0 +1,112 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> + +#include "base/file_util.h" +#include "chrome/browser/view_ids.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/test/automation/constrained_window_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/window_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "chrome/views/event.h" +#include "net/base/net_util.h" + +class InteractiveConstrainedWindowTest : public UITest { + protected: + InteractiveConstrainedWindowTest() { + show_window_ = true; + } +}; + +TEST_F(InteractiveConstrainedWindowTest, UserActivatedResizeToLeavesSpaceForChrome) { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + + scoped_ptr<WindowProxy> window( + automation()->GetWindowForBrowser(browser.get())); + ASSERT_TRUE(window.get()); + + scoped_ptr<TabProxy> tab(browser->GetTab(0)); + ASSERT_TRUE(tab.get()); + + std::wstring filename(test_data_directory_); + file_util::AppendToPath(&filename, L"constrained_files"); + file_util::AppendToPath(&filename, + L"constrained_window_onload_resizeto.html"); + ASSERT_TRUE(tab->NavigateToURL(net_util::FilePathToFileURL(filename))); + + gfx::Rect tab_view_bounds; + ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, + &tab_view_bounds, true)); + + // Simulate a click of the actual link to force user_gesture to be + // true; if we don't, the resulting popup will be constrained, which + // isn't what we want to test. + POINT link_point(tab_view_bounds.CenterPoint().ToPOINT()); + ASSERT_TRUE(window->SimulateOSClick(link_point, + ChromeViews::Event::EF_LEFT_BUTTON_DOWN)); + + ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 1000)); + + scoped_ptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1)); + scoped_ptr<WindowProxy> popup_window( + automation()->GetWindowForBrowser(popup_browser.get())); + + // Make sure we were created with the correct width and height. + gfx::Rect rect; + bool is_timeout = false; + ASSERT_TRUE(popup_window->GetViewBoundsWithTimeout( + VIEW_ID_TAB_CONTAINER, &rect, false, 1000, &is_timeout)); + ASSERT_FALSE(is_timeout); + ASSERT_EQ(rect.width(), 300); + ASSERT_EQ(rect.height(), 320); + + // Send a click to the popup window to test resizeTo. + ASSERT_TRUE(popup_window->GetViewBounds(VIEW_ID_TAB_CONTAINER, + &tab_view_bounds, true)); + POINT popup_link_point(tab_view_bounds.CenterPoint().ToPOINT()); + ASSERT_TRUE(popup_window->SimulateOSClick( + popup_link_point, ChromeViews::Event::EF_LEFT_BUTTON_DOWN)); + + // No idea how to wait here other then sleeping. + Sleep(1000); + + // The actual content will be LESS than (200, 200) because resizeTo + // deals with a window's outer{Width,Height} instead of its + // inner{Width,Height}. + is_timeout = false; + ASSERT_TRUE(popup_window->GetViewBoundsWithTimeout( + VIEW_ID_TAB_CONTAINER, &rect, false, 1000, &is_timeout)); + ASSERT_FALSE(is_timeout); + ASSERT_LT(rect.width(), 200); + ASSERT_LT(rect.height(), 200); +} diff --git a/chrome/browser/views/delay_view.cc b/chrome/browser/views/delay_view.cc new file mode 100644 index 0000000..fd964ea --- /dev/null +++ b/chrome/browser/views/delay_view.cc @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/delay_view.h" + +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +// The amount of horizontal space between the throbber and the label. +const int kThrobberLabelSpace = 7; + +// The amount of space between controls and the edge of the window. +const int kWindowMargin = 5; + +DelayView::DelayView(const std::wstring& text, CommandController* controller, + bool show_cancel) + : controller_(controller), + label_(NULL), + cancel_button_(NULL) { + DCHECK(controller); + + label_ = new ChromeViews::Label(text); + AddChildView(label_); + + if (show_cancel) { + cancel_button_ = + new ChromeViews::NativeButton(l10n_util::GetString(IDS_CANCEL)); + cancel_button_->SetID(ID_CANCEL); + cancel_button_->SetListener(this); + AddChildView(cancel_button_); + } + + throbber_ = new ChromeViews::Throbber(50, true); + AddChildView(throbber_); + throbber_->Start(); +} + +DelayView::~DelayView() { +} + +void DelayView::ButtonPressed(ChromeViews::NativeButton *sender) { + if (sender->GetID() == ID_CANCEL) { + controller_->ExecuteCommand(IDCANCEL); + } +} + +void DelayView::Layout() { + if (!GetParent()) + return; + + CSize available; + GetParent()->GetSize(&available); + + if (cancel_button_) { + CSize button_size; + cancel_button_->GetPreferredSize(&button_size); + cancel_button_->SetBounds(available.cx - kWindowMargin - button_size.cx, + available.cy - kWindowMargin - button_size.cy, + button_size.cx, button_size.cy); + } + + DCHECK(label_); + CSize label_size; + label_->GetPreferredSize(&label_size); + + DCHECK(throbber_); + CSize throbber_size; + throbber_->GetPreferredSize(&throbber_size); + + CRect main_rect(0, 0, + throbber_size.cx + kThrobberLabelSpace + label_size.cx, + std::max(throbber_size.cy, label_size.cy)); + + main_rect.MoveToXY((available.cx / 2) - (main_rect.Width() / 2), + (available.cy / 2) - (main_rect.Height() / 2)); + + label_->SetBounds(main_rect.left + throbber_size.cx + kThrobberLabelSpace, + main_rect.top + main_rect.Height() / 2 - label_size.cy / 2, + label_size.cx, + label_size.cy); + + throbber_->SetBounds( + main_rect.left, + main_rect.top + main_rect.Height() / 2 - throbber_size.cy / 2, + throbber_size.cx, + throbber_size.cy); +}
\ No newline at end of file diff --git a/chrome/browser/views/delay_view.h b/chrome/browser/views/delay_view.h new file mode 100644 index 0000000..b463305 --- /dev/null +++ b/chrome/browser/views/delay_view.h @@ -0,0 +1,73 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A simple view that indicates to the user that a time-consuming operation +// is being performed, using a throbber and some explanatory text. + +#ifndef CHROME_BROWSER_VIEWS_DELAY_VIEW_H__ +#define CHROME_BROWSER_VIEWS_DELAY_VIEW_H__ + +#include "chrome/browser/controller.h" +#include "base/basictypes.h" +#include "chrome/views/label.h" +#include "chrome/views/native_button.h" +#include "chrome/views/throbber.h" + +class DelayView : public ChromeViews::View, + public ChromeViews::NativeButton::Listener { + public: + // |text| explains the delay + // |controller| receives notifications when the "cancel" button is pressed + // |show_cancel| determines whether the cancel button is shown + DelayView(const std::wstring& text, + CommandController* controller, + bool show_cancel); + virtual ~DelayView(); + + enum ViewID { + ID_CANCEL = 10000, + }; + + // Overridden from ChromeViews::View + virtual void Layout(); + + // Implemented from ChromeViews::NativeButton::Listener + virtual void ButtonPressed(ChromeViews::NativeButton *sender); + + private: + CommandController* controller_; + + ChromeViews::Label* label_; + ChromeViews::NativeButton* cancel_button_; + ChromeViews::Throbber* throbber_; + + DISALLOW_EVIL_CONSTRUCTORS(DelayView); +}; + +#endif // CHROME_BROWSER_VIEWS_DELAY_VIEW_H__ diff --git a/chrome/browser/views/dom_view.h b/chrome/browser/views/dom_view.h new file mode 100644 index 0000000..07a5f71 --- /dev/null +++ b/chrome/browser/views/dom_view.h @@ -0,0 +1,92 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// DOMView is a ChromeView that displays the content of a web DOM. +// It should be used with data: URLs. + +#ifndef CHROME_BROWSER_VIEWS_DOM_VIEW_H__ +#define CHROME_BROWSER_VIEWS_DOM_VIEW_H__ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "chrome/browser/render_process_host.h" +#include "chrome/browser/dom_ui/dom_ui_host.h" +#include "chrome/views/hwnd_view.h" + +class DOMView : public ChromeViews::HWNDView { + public: + // Construct a DOMView to display the given data: URL. + explicit DOMView(const GURL& contents) + : contents_(contents), initialized_(false), host_(NULL) {} + + virtual ~DOMView() { + if (host_) { + Detach(); + host_->Destroy(); + host_ = NULL; + } + } + + // Initialize the view, causing it to load its contents. This should be + // called once the view has been added to a container. + // If |instance| is not null, then the view will be loaded in the same + // process as the given instance. + bool Init(Profile* profile, SiteInstance* instance) { + if (initialized_) + return true; + initialized_ = true; + + // TODO(timsteele): This should use a separate factory method; e.g + // a DOMUIHostFactory rather than TabContentsFactory, because DOMView's + // should only be associated with instances of DOMUIHost. + TabContentsType type = TabContents::TypeForURL(&contents_); + TabContents* tab_contents = TabContents::CreateWithType(type, + GetViewContainer()->GetHWND(), profile, instance); + host_ = tab_contents->AsDOMUIHost(); + DCHECK(host_); + + ChromeViews::HWNDView::Attach(host_->GetContainerHWND()); + host_->SetupController(profile); + host_->controller()->LoadURL(contents_, PageTransition::START_PAGE); + return true; + } + + protected: + DOMUIHost* host_; + + private: + GURL contents_; + bool initialized_; + + DISALLOW_EVIL_CONSTRUCTORS(DOMView); +}; + +#endif // CHROME_BROWSER_VIEWS_DOM_VIEW_H__ + diff --git a/chrome/browser/views/download_item_view.cc b/chrome/browser/views/download_item_view.cc new file mode 100644 index 0000000..4cd218d --- /dev/null +++ b/chrome/browser/views/download_item_view.cc @@ -0,0 +1,585 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <vector> + +#include "chrome/browser/views/download_item_view.h" + +#include "base/message_loop.h" +#include "base/task.h" +#include "base/timer.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/download_util.h" +#include "chrome/browser/views/download_shelf_view.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/root_view.h" +#include "chrome/views/view_container.h" +#include "generated_resources.h" + +// TODO(paulg): These may need to be adjusted when download progress +// animation is added, and also possibly to take into account +// different screen resolutions. +static const int kTextWidth = 140; // Pixels +static const int kHorizontalTextPadding = 2; // Pixels +static const int kVerticalPadding = 3; // Pixels +static const int kVerticalTextSpacer = 2; // Pixels +static const int kVerticalTextPadding = 2; // Pixels + +// We add some padding before the left image so that the progress animation icon +// hides the corners of the left image. +static const int kLeftPadding = 0; // Pixels. + +static const SkColor kFileNameColor = SkColorSetRGB(87, 108, 149); +static const SkColor kStatusColor = SkColorSetRGB(123, 141, 174); + +// How long the 'download complete' animation should last for. +static const int kCompleteAnimationDurationMs = 2500; + +// DownloadItemView ------------------------------------------------------------ + +DownloadItemView::DownloadItemView(DownloadItem* download, + DownloadShelfView* parent, + DownloadItemView::BaseDownloadItemModel* model) + : download_(download), + parent_(parent), + model_(model), + progress_angle_(download_util::kStartAngleDegrees), + progress_timer_(NULL), + progress_task_(NULL), + body_state_(NORMAL), + drop_down_state_(NORMAL), + drop_down_pressed_(false), + file_name_(download_->file_name()), + status_text_(l10n_util::GetString(IDS_DOWNLOAD_STATUS_STARTING)), + show_status_text_(true), + dragging_(false), + starting_drag_(false) { + // TODO(idana) Bug# 1163334 + // + // We currently do not mirror each download item on the download shelf (even + // though the download shelf itself is mirrored and the items appear from + // right to left on RTL UIs). + // + // We explicitly disable mirroring for the item because the code that draws + // the download progress animation relies on the View's UI layout setting + // when positioning the animation so we should make sure that code doesn't + // treat our View as a mirrored View. + EnableUIMirroringForRTLLanguages(false); + DCHECK(download_); + download_->AddObserver(this); + + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + + BodyImageSet normal_body_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM) + }; + normal_body_image_set_ = normal_body_image_set; + + DropDownImageSet normal_drop_down_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM) + }; + normal_drop_down_image_set_ = normal_drop_down_image_set; + + BodyImageSet hot_body_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H) + }; + hot_body_image_set_ = hot_body_image_set; + + DropDownImageSet hot_drop_down_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H) + }; + hot_drop_down_image_set_ = hot_drop_down_image_set; + + BodyImageSet pushed_body_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P) + }; + pushed_body_image_set_ = pushed_body_image_set; + + DropDownImageSet pushed_drop_down_image_set = { + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P), + rb.GetBitmapNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P) + }; + pushed_drop_down_image_set_ = pushed_drop_down_image_set; + + LoadIcon(); + + font_ = ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); + box_height_ = std::max<int>(2 * kVerticalPadding + font_.height() + + kVerticalTextPadding + font_.height(), + 2 * kVerticalPadding + + normal_body_image_set_.top_left->height() + + normal_body_image_set_.bottom_left->height()); + + int progress_icon_size = + download_util::GetProgressIconSize(download_util::SMALL); + if (progress_icon_size > box_height_) + box_y_ = (progress_icon_size - box_height_) / 2; + else + box_y_ = kVerticalPadding; + + CSize size; + GetPreferredSize(&size); + drop_down_x_ = size.cx - normal_drop_down_image_set_.top->width(); + + body_hover_animation_.reset(new SlideAnimation(this)); + drop_hover_animation_.reset(new SlideAnimation(this)); + + // Set up our animation + StartDownloadProgress(); +} + +DownloadItemView::~DownloadItemView() { + icon_consumer_.CancelAllRequests(); + StopDownloadProgress(); + download_->RemoveObserver(this); +} + +// Progress animation handlers + +void DownloadItemView::UpdateDownloadProgress() { + progress_angle_ = (progress_angle_ + + download_util::kUnknownIncrementDegrees) % + download_util::kMaxDegrees; + SchedulePaint(); +} + +void DownloadItemView::StartDownloadProgress() { + if (progress_task_ || progress_timer_) + return; + progress_task_ = + new download_util::DownloadProgressTask<DownloadItemView>(this); + progress_timer_ = + MessageLoop::current()->timer_manager()-> + StartTimer(download_util::kProgressRateMs, progress_task_, true); +} + +void DownloadItemView::StopDownloadProgress() { + if (progress_timer_) { + DCHECK(progress_task_); + MessageLoop::current()->timer_manager()->StopTimer(progress_timer_); + delete progress_timer_; + progress_timer_ = NULL; + delete progress_task_; + progress_task_ = NULL; + } +} + +// DownloadObserver interface + +// Update the progress graphic on the icon and our text status label +// to reflect our current bytes downloaded, time remaining. +void DownloadItemView::OnDownloadUpdated(DownloadItem* download) { + DCHECK(download == download_); + + std::wstring status_text = model_->GetStatusText(); + switch (download_->state()) { + case DownloadItem::IN_PROGRESS: + download_->is_paused() ? StopDownloadProgress() : StartDownloadProgress(); + break; + case DownloadItem::COMPLETE: + StopDownloadProgress(); + complete_animation_.reset(new SlideAnimation(this)); + complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs); + complete_animation_->SetTweenType(SlideAnimation::NONE); + complete_animation_->Show(); + if (status_text.empty()) + show_status_text_ = false; + SchedulePaint(); + LoadIcon(); + break; + case DownloadItem::CANCELLED: + StopDownloadProgress(); + LoadIcon(); + break; + case DownloadItem::REMOVING: + parent_->RemoveDownloadView(this); // This will delete us! + return; + default: + NOTREACHED(); + } + + status_text_ = status_text; + + // We use the parent's (DownloadShelfView's) SchedulePaint, since there + // are spaces between each DownloadItemView that the parent is responsible + // for painting. + GetParent()->SchedulePaint(); +} + +// View overrides + +// Load an icon for the file type we're downloading, and animate any in progress +// download state. +void DownloadItemView::Paint(ChromeCanvas* canvas) { + int height = GetHeight(); + int center_width = GetWidth() - kLeftPadding - + normal_body_image_set_.left->width() - + normal_body_image_set_.right->width() - + normal_drop_down_image_set_.center->width(); + + // May be caused by animation. + if (center_width <= 0) + return; + + BodyImageSet* body_image_set; + switch (body_state_) { + case NORMAL: + case HOT: + body_image_set = &normal_body_image_set_; + break; + case PUSHED: + body_image_set = &pushed_body_image_set_; + break; + default: + NOTREACHED(); + } + DropDownImageSet* drop_down_image_set; + switch (drop_down_state_) { + case NORMAL: + case HOT: + drop_down_image_set = &normal_drop_down_image_set_; + break; + case PUSHED: + drop_down_image_set = &pushed_drop_down_image_set_; + break; + default: + NOTREACHED(); + } + + // Paint the background images. + int x = kLeftPadding; + PaintBitmaps(canvas, + body_image_set->top_left, body_image_set->left, + body_image_set->bottom_left, + x, box_y_, box_height_, body_image_set->top_left->width()); + x += body_image_set->top_left->width(); + PaintBitmaps(canvas, + body_image_set->top, body_image_set->center, + body_image_set->bottom, + x, box_y_, box_height_, center_width); + x += center_width; + PaintBitmaps(canvas, + body_image_set->top_right, body_image_set->right, + body_image_set->bottom_right, + x, box_y_, box_height_, body_image_set->top_right->width()); + + // Overlay our body hot state. + if (body_hover_animation_->GetCurrentValue() > 0) { + canvas->saveLayerAlpha(NULL, + static_cast<int>(body_hover_animation_->GetCurrentValue() * 255), + SkCanvas::kARGB_NoClipLayer_SaveFlag); + canvas->drawARGB(0, 255, 255, 255, SkPorterDuff::kClear_Mode); + + int x = kLeftPadding; + PaintBitmaps(canvas, + hot_body_image_set_.top_left, hot_body_image_set_.left, + hot_body_image_set_.bottom_left, + x, box_y_, box_height_, hot_body_image_set_.top_left->width()); + x += body_image_set->top_left->width(); + PaintBitmaps(canvas, + hot_body_image_set_.top, hot_body_image_set_.center, + hot_body_image_set_.bottom, + x, box_y_, box_height_, center_width); + x += center_width; + PaintBitmaps(canvas, + hot_body_image_set_.top_right, hot_body_image_set_.right, + hot_body_image_set_.bottom_right, + x, box_y_, box_height_, hot_body_image_set_.top_right->width()); + canvas->restore(); + } + + x += body_image_set->top_right->width(); + PaintBitmaps(canvas, + drop_down_image_set->top, drop_down_image_set->center, + drop_down_image_set->bottom, + x, box_y_, box_height_, drop_down_image_set->top->width()); + + // Overlay our drop-down hot state. + if (drop_hover_animation_->GetCurrentValue() > 0) { + canvas->saveLayerAlpha(NULL, + static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255), + SkCanvas::kARGB_NoClipLayer_SaveFlag); + canvas->drawARGB(0, 255, 255, 255, SkPorterDuff::kClear_Mode); + + PaintBitmaps(canvas, + drop_down_image_set->top, drop_down_image_set->center, + drop_down_image_set->bottom, + x, box_y_, box_height_, drop_down_image_set->top->width()); + + canvas->restore(); + } + + // Print the text, left aligned. + int progress_icon_size = + download_util::GetProgressIconSize(download_util::SMALL); + // Last value of x was the end of the right image, just before the button. + if (show_status_text_) { + int y = box_y_ + kVerticalPadding; + canvas->DrawStringInt(file_name_, font_, kFileNameColor, + progress_icon_size, y, kTextWidth, font_.height()); + y += font_.height() + kVerticalTextPadding; + + canvas->DrawStringInt(status_text_, font_, kStatusColor, + progress_icon_size, y, kTextWidth, font_.height()); + } else { + int y = box_y_ + (box_height_ - font_.height()) / 2; + canvas->DrawStringInt(file_name_, font_, kFileNameColor, + progress_icon_size, y, kTextWidth, font_.height()); + } + + // Paint the icon. + IconManager* im = g_browser_process->icon_manager(); + SkBitmap* icon = im->LookupIcon(download_->full_path(), IconLoader::SMALL); + + if (icon) { + if (download_->state() == DownloadItem::IN_PROGRESS) { + download_util::PaintDownloadProgress(canvas, this, 0, 0, + progress_angle_, + download_->PercentComplete(), + download_util::SMALL); + } else if (download_->state() == DownloadItem::COMPLETE && + complete_animation_->IsAnimating()) { + download_util::PaintDownloadComplete(canvas, this, 0, 0, + complete_animation_->GetCurrentValue(), + download_util::SMALL); + } + + // Draw the icon image + int offset = download_util::GetProgressIconOffset(download_util::SMALL); + canvas->DrawBitmapInt(*icon, offset, offset); + } +} + +void DownloadItemView::PaintBitmaps(ChromeCanvas* canvas, + const SkBitmap* top_bitmap, + const SkBitmap* center_bitmap, + const SkBitmap* bottom_bitmap, + int x, int y, int height, int width) { + int middle_height = height - top_bitmap->height() - bottom_bitmap->height(); + // Draw the top. + canvas->DrawBitmapInt(*top_bitmap, + 0, 0, top_bitmap->width(), top_bitmap->height(), + x, y, width, top_bitmap->height(), false); + y += top_bitmap->height(); + // Draw the center. + canvas->DrawBitmapInt(*center_bitmap, + 0, 0, center_bitmap->width(), center_bitmap->height(), + x, y, width, middle_height, false); + y += middle_height; + // Draw the bottom. + canvas->DrawBitmapInt(*bottom_bitmap, + 0, 0, bottom_bitmap->width(), bottom_bitmap->height(), + x, y, width, bottom_bitmap->height(), false); +} + +void DownloadItemView::SetState(State body_state, State drop_down_state) { + if (body_state_ == body_state && drop_down_state_ == drop_down_state) + return; + + body_state_ = body_state; + drop_down_state_ = drop_down_state; + SchedulePaint(); +} + +void DownloadItemView::GetPreferredSize(CSize* out) { + int width = kLeftPadding + normal_body_image_set_.top_left->width(); + width += download_util::GetProgressIconSize(download_util::SMALL); + width += kTextWidth; + width += normal_body_image_set_.top_right->width(); + width += normal_drop_down_image_set_.top->width(); + + out->cx = width; + out->cy = std::max<int>( + 2 * kVerticalPadding + 2 * font_.height() + kVerticalTextPadding, + download_util::GetProgressIconSize(download_util::SMALL)); +} + +void DownloadItemView::OnMouseExited(const ChromeViews::MouseEvent& event) { + SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL); + body_hover_animation_->Hide(); + drop_hover_animation_->Hide(); +} + +// Display the context menu for this item. +bool DownloadItemView::OnMousePressed(const ChromeViews::MouseEvent& event) { + // Stop any completion animation. + if (complete_animation_.get() && complete_animation_->IsAnimating()) + complete_animation_->End(); + + if (event.IsOnlyLeftMouseButton()) { + CPoint point(event.GetLocation()); + if (event.GetX() < drop_down_x_) { + SetState(PUSHED, NORMAL); + return true; + } + + drop_down_pressed_ = true; + SetState(NORMAL, PUSHED); + + // Similar hack as in MenuButton. + // We're about to show the menu from a mouse press. By showing from the + // mouse press event we block RootView in mouse dispatching. This also + // appears to cause RootView to get a mouse pressed BEFORE the mouse + // release is seen, which means RootView sends us another mouse press no + // matter where the user pressed. To force RootView to recalculate the + // mouse target during the mouse press we explicitly set the mouse handler + // to NULL. + GetRootView()->SetMouseHandler(NULL); + + // The menu's position is different depending on the UI layout. + // DownloadShelfContextMenu will take care of setting the right anchor for + // the menu depending on the locale. + // + // TODO(idana): when bug# 1163334 is fixed the following check should be + // replaced with UILayoutIsRightToLeft(). + point.y = GetHeight(); + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) { + point.x = GetWidth(); + } else { + point.x = drop_down_x_; + } + + ChromeViews::View::ConvertPointToScreen(this, &point); + download_util::DownloadShelfContextMenu menu(download_, + GetViewContainer()->GetHWND(), + model_.get(), + point); + drop_down_pressed_ = false; + // Showing the menu blocks. Here we revert the state. + SetState(NORMAL, NORMAL); + } + return true; +} + +void DownloadItemView::OnMouseMoved(const ChromeViews::MouseEvent& event) { + bool on_body = event.GetX() < drop_down_x_; + SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT); + if (on_body) { + body_hover_animation_->Show(); + drop_hover_animation_->Hide(); + } else { + body_hover_animation_->Hide(); + drop_hover_animation_->Show(); + } +} + +void DownloadItemView::OnMouseReleased(const ChromeViews::MouseEvent& event, + bool canceled) { + if (dragging_) { + // Starting a drag results in a MouseReleased, we need to ignore it. + dragging_ = false; + starting_drag_ = false; + return; + } + CPoint point(event.GetLocation()); + if (event.IsOnlyLeftMouseButton() && event.GetX() < drop_down_x_) + OpenDownload(); + + SetState(NORMAL, NORMAL); +} + +// Handle drag (file copy) operations. +bool DownloadItemView::OnMouseDragged(const ChromeViews::MouseEvent& event) { + if (!starting_drag_) { + starting_drag_ = true; + drag_start_point_ = event.location(); + } + if (dragging_) { + if (download_->state() == DownloadItem::COMPLETE) { + IconManager* im = g_browser_process->icon_manager(); + SkBitmap* icon = im->LookupIcon(download_->full_path(), + IconLoader::SMALL); + if (icon) + download_util::DragDownload(download_, icon); + } + } else if (win_util::IsDrag(drag_start_point_.ToPOINT(), + event.location().ToPOINT())) { + dragging_ = true; + } + return true; +} + +void DownloadItemView::AnimationProgressed(const Animation* animation) { + // We don't care if what animation (body button/drop button/complete), + // is calling back, as they all have to go through the same paint call. + SchedulePaint(); +} + +void DownloadItemView::OpenDownload() { + if (download_->state() == DownloadItem::IN_PROGRESS) { + download_->set_open_when_complete(!download_->open_when_complete()); + } else if (download_->state() == DownloadItem::COMPLETE) { + download_util::OpenDownload(download_); + } +} + +void DownloadItemView::OnExtractIconComplete(IconManager::Handle handle, + SkBitmap* icon_bitmap) { + GetParent()->SchedulePaint(); +} + +void DownloadItemView::LoadIcon() { + IconManager* im = g_browser_process->icon_manager(); + im->LoadIcon(download_->full_path(), IconLoader::SMALL, + &icon_consumer_, + NewCallback(this, &DownloadItemView::OnExtractIconComplete)); +} diff --git a/chrome/browser/views/download_item_view.h b/chrome/browser/views/download_item_view.h new file mode 100644 index 0000000..0a03732 --- /dev/null +++ b/chrome/browser/views/download_item_view.h @@ -0,0 +1,222 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// A ChromeView that implements one download on the Download shelf. +// Each DownloadItemView contains an application icon, a text label +// indicating the download's file name, a text label indicating the +// download's status (such as the number of bytes downloaded so far) +// and a button for canceling an in progress download, or opening +// the completed download. +// +// The DownloadItemView lives in the Browser, and has a corresponding +// DownloadController that receives / writes data which lives in the +// Renderer. + +#ifndef CHROME_BROWSER_VIEWS_DOWNLOAD_ITEM_VIEW_H__ +#define CHROME_BROWSER_VIEWS_DOWNLOAD_ITEM_VIEW_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/common/slide_animation.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/download_manager.h" +#include "chrome/browser/icon_manager.h" +#include "chrome/views/event.h" +#include "chrome/views/view.h" +#include "chrome/views/label.h" + +class DownloadShelfView; +class SkBitmap; +class Task; +class Timer; + +class DownloadItemView : public ChromeViews::View, + public DownloadItem::Observer, + public AnimationDelegate { + public: + // This class provides functions which have different behaviors between + // download and saving page. + class BaseDownloadItemModel { + public: + // Cancel the task corresponding to the item. + virtual void CancelTask() = 0; + + // Get the status text to display. + virtual std::wstring GetStatusText() = 0; + }; + + DownloadItemView(DownloadItem* download, + DownloadShelfView* parent, + BaseDownloadItemModel* model); + virtual ~DownloadItemView(); + + // DownloadObserver method + virtual void OnDownloadUpdated(DownloadItem* download); + + // View overrides + virtual void Paint(ChromeCanvas* canvas); + virtual void GetPreferredSize(CSize *out); + virtual void OnMouseExited(const ChromeViews::MouseEvent& event); + virtual void OnMouseMoved(const ChromeViews::MouseEvent& event); + virtual bool OnMousePressed(const ChromeViews::MouseEvent& event); + virtual void OnMouseReleased(const ChromeViews::MouseEvent& event, + bool canceled); + virtual bool OnMouseDragged(const ChromeViews::MouseEvent& event); + + // AnimationDelegate implementation. + virtual void AnimationProgressed(const Animation* animation); + + // Timer callback for handling animations + void UpdateDownloadProgress(); + void StartDownloadProgress(); + void StopDownloadProgress(); + + // IconManager::Client interface. + void OnExtractIconComplete(IconManager::Handle handle, SkBitmap* icon_bitmap); + + private: + enum State { + NORMAL = 0, + HOT, + PUSHED, + }; + + // The image set associated with the part containing the icon and text. + struct BodyImageSet { + SkBitmap* top_left; + SkBitmap* left; + SkBitmap* bottom_left; + SkBitmap* top; + SkBitmap* center; + SkBitmap* bottom; + SkBitmap* top_right; + SkBitmap* right; + SkBitmap* bottom_right; + }; + + // The image set associated with the drop-down button on the right. + struct DropDownImageSet { + SkBitmap* top; + SkBitmap* center; + SkBitmap* bottom; + }; + + void OpenDownload(); + + void LoadIcon(); + + // Convenience method to paint the 3 vertical bitmaps (bottom, middle, top) + // that form the background. + void PaintBitmaps(ChromeCanvas* canvas, + const SkBitmap* top_bitmap, + const SkBitmap* center_bitmap, + const SkBitmap* bottom_bitmap, + int x, + int y, + int height, + int width); + + // Sets the state and triggers a repaint. + void SetState(State body_state, State drop_down_state); + + // The different images used for the background. + BodyImageSet normal_body_image_set_; + BodyImageSet hot_body_image_set_; + BodyImageSet pushed_body_image_set_; + DropDownImageSet normal_drop_down_image_set_; + DropDownImageSet hot_drop_down_image_set_; + DropDownImageSet pushed_drop_down_image_set_; + + // The model we query for display information + DownloadItem* download_; + + // Our parent view that owns us. + DownloadShelfView* parent_; + + // Elements of our particular download + std::wstring file_name_; + std::wstring status_text_; + bool show_status_text_; + + // The font used to print the file name and status. + ChromeFont font_; + + // The current state (normal, hot or pushed) of the body and drop-down. + State body_state_; + State drop_down_state_; + + // In degrees, for downloads with no known total size. + int progress_angle_; + + // The x coordinate at which the drop-down button starts. + int drop_down_x_; + + // Used when we are showing the menu to show the drop-down as pressed. + bool drop_down_pressed_; + + // The height of the box formed by the background images and its labels. + int box_height_; + + // The y coordinate of the box formed by the background images and its labels. + int box_y_; + + // Whether we are dragging the download button. + bool dragging_; + + // Whether we are tracking a possible drag. + bool starting_drag_; + + // Position that a possible drag started at. + gfx::Point drag_start_point_; + + // For canceling an in progress icon request. + CancelableRequestConsumerT<int, 0> icon_consumer_; + + // A model class to control the status text we display and the cancel + // behavior. + // This class owns the pointer. + scoped_ptr<BaseDownloadItemModel> model_; + + // Hover animations for our body and drop buttons. + scoped_ptr<SlideAnimation> body_hover_animation_; + scoped_ptr<SlideAnimation> drop_hover_animation_; + + // Animation for download complete. + scoped_ptr<SlideAnimation> complete_animation_; + + // Progress animation + Timer* progress_timer_; + Task* progress_task_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadItemView); +}; + +#endif // CHROME_BROWSER_VIEWS_DOWNLOAD_ITEM_VIEW_H__ diff --git a/chrome/browser/views/download_shelf_view.cc b/chrome/browser/views/download_shelf_view.cc new file mode 100644 index 0000000..49f7f67 --- /dev/null +++ b/chrome/browser/views/download_shelf_view.cc @@ -0,0 +1,300 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/download_shelf_view.h" + +#include <algorithm> + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/download_item_model.h" +#include "chrome/browser/download_manager.h" +#include "chrome/browser/download_tab_view.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/views/download_item_view.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "base/logging.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" +#include "chrome/views/button.h" +#include "chrome/views/image_view.h" + +#include "generated_resources.h" + +// Max number of download views we'll contain. Any time a view is added and +// we already have this many download views, one is removed. +static const int kMaxDownloadViews = 15; + +// Padding from left edge and first download view. +static const int kLeftPadding = 2; + +// Padding from right edge and close button/show downloads link. +static const int kRightPadding = 10; + +// Padding between the show all link and close button. +static const int kCloseAndLinkPadding = 14; + +// Padding between the download views. +static const int kDownloadPadding = 10; + +// Padding between the top/bottom and the content. +static const int kTopBottomPadding = 2; + +// Padding between the icon and 'show all downloads' link +static const int kDownloadsTitlePadding = 4; + +// Default background color for the shelf. +static const SkColor kBackgroundColor = SkColorSetRGB(230, 237, 244); + +// Border color. +static const SkColor kBorderColor = SkColorSetRGB(214, 214, 214); + +// New download item animation speed in milliseconds. +static const int kNewItemAnimationDurationMs = 800; + +// Shelf show/hide speed. +static const int kShelfAnimationDurationMs = 120; + +namespace { + +// Sets size->cx to view's preferred width + size->cx. +// Sets size->cy to the max of the view's preferred height and size->cy; +void AdjustSize(ChromeViews::View* view, CSize* size) { + CSize view_preferred; + view->GetPreferredSize(&view_preferred); + size->cx += view_preferred.cx; + size->cy = std::max(view_preferred.cy, size->cy); +} + +int CenterPosition(int size, int target_size) { + return std::max((target_size - size) / 2, kTopBottomPadding); +} + +} // namespace + +DownloadShelfView::DownloadShelfView(TabContents* tab_contents) + : tab_contents_(tab_contents) { + Init(); +} + +void DownloadShelfView::Init() { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + arrow_image_ = new ChromeViews::ImageView(); + arrow_image_->SetImage(rb.GetBitmapNamed(IDR_DOWNLOADS_FAVICON)); + AddChildView(arrow_image_); + + show_all_view_ = + new ChromeViews::Link(l10n_util::GetString(IDS_SHOW_ALL_DOWNLOADS)); + show_all_view_->SetController(this); + AddChildView(show_all_view_); + + close_button_ = new ChromeViews::Button(); + close_button_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_CLOSE_BAR)); + close_button_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_CLOSE_BAR_H)); + close_button_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_CLOSE_BAR_P)); + close_button_->SetListener(this, 0); + AddChildView(close_button_); + SetBackground( + ChromeViews::Background::CreateSolidBackground(kBackgroundColor)); + + new_item_animation_.reset(new SlideAnimation(this)); + new_item_animation_->SetSlideDuration(kNewItemAnimationDurationMs); + + shelf_animation_.reset(new SlideAnimation(this)); + shelf_animation_->SetSlideDuration(kShelfAnimationDurationMs); + shelf_animation_->Show(); +} + +void DownloadShelfView::AddDownloadView(View* view) { + DCHECK(view); + download_views_.push_back(view); + AddChildView(view); + if (download_views_.size() > kMaxDownloadViews) + RemoveDownloadView(*download_views_.begin()); + + new_item_animation_->Reset(); + new_item_animation_->Show(); +} + +void DownloadShelfView::ChangeTabContents(TabContents* old_contents, + TabContents* new_contents) { + DCHECK(old_contents == tab_contents_); + tab_contents_ = new_contents; +} + +void DownloadShelfView::AddDownload(DownloadItem* download) { + shelf_animation_->Show(); + + DownloadItemView* view = new DownloadItemView( + download, this, new DownloadItemModel(download)); + AddDownloadView(view); +} + +void DownloadShelfView::RemoveDownloadView(View* view) { + DCHECK(view); + std::vector<View*>::iterator i = + find(download_views_.begin(), download_views_.end(), view); + DCHECK(i != download_views_.end()); + download_views_.erase(i); + RemoveChildView(view); + delete view; + if (download_views_.empty()) + tab_contents_->SetDownloadShelfVisible(false); + Layout(); + SchedulePaint(); +} + +void DownloadShelfView::Paint(ChromeCanvas* canvas) { + PaintBackground(canvas); + PaintBorder(canvas); +} + +void DownloadShelfView::PaintBorder(ChromeCanvas* canvas) { + canvas->FillRectInt(kBorderColor, 0, 0, GetWidth(), 1); +} + +void DownloadShelfView::GetPreferredSize(CSize *out) { + out->cx = kRightPadding + kLeftPadding + kCloseAndLinkPadding; + out->cy = 0; + AdjustSize(close_button_, out); + AdjustSize(show_all_view_, out); + // Add one download view to the preferred size. + if (download_views_.size() > 0) { + AdjustSize(*download_views_.begin(), out); + out->cx += kDownloadPadding; + } + out->cy += kTopBottomPadding + kTopBottomPadding; + if (shelf_animation_->IsAnimating()) { + out->cy = static_cast<int>(static_cast<double>(out->cy) * + shelf_animation_->GetCurrentValue()); + } +} + +void DownloadShelfView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void DownloadShelfView::AnimationProgressed(const Animation *animation) { + if (animation == new_item_animation_.get()) { + Layout(); + SchedulePaint(); + } else if (animation == shelf_animation_.get()) { + // Force a re-layout of the parent, which will call back into + // GetPreferredSize, where we will do our animation. In the case where the + // animation is hiding, we do a full resize - the fast resizing would + // otherwise leave blank white areas where the shelf was and where the + // user's eye is. Thankfully bottom-resizing is a lot faster than + // top-resizing. + tab_contents_->ToolbarSizeChanged(shelf_animation_->IsShowing()); + } +} + +void DownloadShelfView::AnimationEnded(const Animation *animation) { + if (animation == shelf_animation_.get()) { + if (shelf_animation_->IsShowing() == false) + tab_contents_->SetDownloadShelfVisible(false); + tab_contents_->ToolbarSizeChanged(false); + } +} + +void DownloadShelfView::Layout() { + int width = GetWidth(); + int height = GetHeight(); + CSize image_size; + arrow_image_->GetPreferredSize(&image_size); + CSize close_button_size; + close_button_->GetPreferredSize(&close_button_size); + CSize show_all_size; + show_all_view_->GetPreferredSize(&show_all_size); + int max_download_x = + std::max<int>(0, width - kRightPadding - close_button_size.cx - + kCloseAndLinkPadding - show_all_size.cx - + image_size.cx - kDownloadPadding); + int next_x = max_download_x + kDownloadPadding; + // Align vertically with show_all_view_. + arrow_image_->SetBounds(next_x, CenterPosition(show_all_size.cy, height), + image_size.cx, image_size.cy); + next_x += image_size.cx + kDownloadsTitlePadding; + show_all_view_->SetBounds(next_x, + CenterPosition(show_all_size.cy, height), + show_all_size.cx, + show_all_size.cy); + next_x += show_all_size.cx + kCloseAndLinkPadding; + close_button_->SetBounds(next_x, + CenterPosition(close_button_size.cy, height), + close_button_size.cx, + close_button_size.cy); + + next_x = kLeftPadding; + std::vector<View*>::reverse_iterator ri; + for (ri = download_views_.rbegin(); ri != download_views_.rend(); ++ri) { + CSize view_size; + (*ri)->GetPreferredSize(&view_size); + + int x = next_x; + + // Figure out width of item. + int width = view_size.cx; + if (new_item_animation_->IsAnimating() && ri == download_views_.rbegin()) { + width = static_cast<int>(static_cast<double>(view_size.cx) * + new_item_animation_->GetCurrentValue()); + } + + next_x += (width + kDownloadPadding); + + // Make sure our item can be contained within the shelf. + if (next_x < max_download_x) { + (*ri)->SetVisible(true); + (*ri)->SetBounds(x, CenterPosition(view_size.cy, height), width, + view_size.cy); + } else { + (*ri)->SetVisible(false); + } + } +} + +// Open the download page. +void DownloadShelfView::LinkActivated(ChromeViews::Link* source, + int event_flags) { + int index; + NavigationController* controller = tab_contents_->controller(); + Browser* browser = Browser::GetBrowserForController(controller, &index); + DCHECK(browser); + browser->ShowNativeUI(DownloadTabUI::GetURL()); +} + +void DownloadShelfView::ButtonPressed(ChromeViews::BaseButton* button) { + shelf_animation_->Hide(); +}
\ No newline at end of file diff --git a/chrome/browser/views/download_shelf_view.h b/chrome/browser/views/download_shelf_view.h new file mode 100644 index 0000000..2c98ec3 --- /dev/null +++ b/chrome/browser/views/download_shelf_view.h @@ -0,0 +1,139 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_DOWNLOAD_SHELF_VIEW_H__ +#define CHROME_BROWSER_VIEWS_DOWNLOAD_SHELF_VIEW_H__ + +#include "chrome/common/slide_animation.h" +#include "chrome/views/button.h" +#include "chrome/views/link.h" + +namespace ChromeViews { + class ImageView; +} + +class DownloadItem; +class TabContents; + +class DownloadAnimation; + +// DownloadShelfView is a view that contains individual views for each download, +// as well as a close button and a link to show all downloads. +// +// To add a view representing a download to DownloadShelfView, invoke +// AddDownloadView. AddDownloadView takes ownership of the passed in View. +// DownloadShelfView does not hold an infinite number of download views, rather +// it'll automatically remove views once a certain point is reached. As such, +// the remove method is private. +class DownloadShelfView : public ChromeViews::View, + public ChromeViews::BaseButton::ButtonListener, + public ChromeViews::LinkController, + public AnimationDelegate { + public: + DownloadShelfView(TabContents* tab_contents); + + // A new download has started, so add it to our shelf. + void AddDownload(DownloadItem* download); + + virtual void GetPreferredSize(CSize *out); + + virtual void Layout(); + + // Invokes the following methods to do painting: + // PaintBackground, PaintBorder and PaintSeparators. + virtual void Paint(ChromeCanvas* canvas); + + void DidChangeBounds(const CRect& previous, const CRect& current); + + // AnimationDelegate implementations + virtual void AnimationProgressed(const Animation* animation); + virtual void AnimationEnded(const Animation* animation); + + // Invoked when the user clicks the 'show all downloads' link button. + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + // Invoked when the user clicks the close button. Asks the browser to + // hide the download shelf. + virtual void ButtonPressed(ChromeViews::BaseButton* button); + + // Removes a specified download view. The supplied view is deleted after + // it's removed. + void RemoveDownloadView(ChromeViews::View* view); + + // Adds a View representing a download to this DownloadShelfView. + // DownloadShelfView takes ownership of the View, and will delete it as + // necessary. + void AddDownloadView(ChromeViews::View* view); + + // Invoked when the download shelf is migrated from one tab contents to a new + // one. + void ChangeTabContents(TabContents* old_contents, TabContents* new_contents); + + private: + void Init(); + + // Paints the border. + void PaintBorder(ChromeCanvas* canvas); + + // Paints the separators. This invokes PaintSeparator to paint a particular + // separator. + void PaintSeparators(ChromeCanvas* canvas); + + // Paints the separator between the two views. + void PaintSeparator(ChromeCanvas* canvas, + ChromeViews::View* v1, + ChromeViews::View* v2); + + TabContents* tab_contents_; + + // The animation for adding new items to the shelf. + scoped_ptr<SlideAnimation> new_item_animation_; + + // The show/hide animation for the shelf itself. + scoped_ptr<SlideAnimation> shelf_animation_; + + // The download views. These are also child Views, and deleted when + // the DownloadShelfView is deleted. + std::vector<View*> download_views_; + + // An image displayed on the right of the "Show all downloads..." link. + ChromeViews::ImageView* arrow_image_; + + // Link for showing all downloads. This is contained as a child, and deleted + // by View. + ChromeViews::Link* show_all_view_; + + // Button for closing the downloads. This is contained as a child, and + // deleted by View. + ChromeViews::Button* close_button_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadShelfView); +}; + +#endif // CHROME_BROWSER_VIEWS_DOWNLOAD_SHELF_VIEW_H__ diff --git a/chrome/browser/views/download_started_animation.cc b/chrome/browser/views/download_started_animation.cc new file mode 100644 index 0000000..3131a31 --- /dev/null +++ b/chrome/browser/views/download_started_animation.cc @@ -0,0 +1,131 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/download_started_animation.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/hwnd_view_container.h" + +// How long to spend moving downwards and fading out after waiting. +static const int kMoveTimeMs = 600; + +// The animation framerate. +static const int kFrameRateHz = 60; + +// What fraction of the frame height to move downward from the frame center. +// Note that setting this greater than 0.5 will mean moving past the bottom of +// the frame. +static const double kMoveFraction = 1.0 / 3.0; + +DownloadStartedAnimation::DownloadStartedAnimation(TabContents* tab_contents) + : Animation(kMoveTimeMs, kFrameRateHz, NULL), + popup_(NULL), + tab_contents_(tab_contents) { + static SkBitmap* kDownloadImage = NULL; + if (!kDownloadImage) { + kDownloadImage = ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_DOWNLOAD_ANIMATION_BEGIN); + } + + // If we're too small to show the download image, then don't bother - + // the shelf will be enough. + tab_contents_->GetContainerBounds(&tab_contents_bounds_); + if (tab_contents_bounds_.height() < kDownloadImage->height()) + return; + + NotificationService::current()->AddObserver(this, NOTIFY_TAB_CONTENTS_HIDDEN, + Source<TabContents>(tab_contents_)); + NotificationService::current()->AddObserver(this, + NOTIFY_TAB_CONTENTS_DESTROYED, Source<TabContents>(tab_contents_)); + + SetImage(kDownloadImage); + + gfx::Rect rc(0, 0, 0, 0); + popup_ = new ChromeViews::HWNDViewContainer; + popup_->set_window_style(WS_POPUP); + popup_->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW | + WS_EX_TRANSPARENT); + popup_->SetLayeredAlpha(0x00); + popup_->Init(tab_contents_->GetContainerHWND(), rc, this, false); + Reposition(); + popup_->ShowWindow(SW_SHOWNOACTIVATE); + + Start(); +} + +void DownloadStartedAnimation::Reposition() { + if (!tab_contents_) + return; + + // Align the image with the bottom left of the web contents (so that it + // points to the newly created download). + CSize size; + GetPreferredSize(&size); + popup_->MoveWindow(tab_contents_bounds_.x(), + static_cast<int>(tab_contents_bounds_.bottom() - size.cy - + size.cy * (1 - GetCurrentValue())), + size.cx, size.cy); +} + +void DownloadStartedAnimation::Close() { + if (!tab_contents_) + return; + + NotificationService::current()->RemoveObserver(this, + NOTIFY_TAB_CONTENTS_HIDDEN, Source<TabContents>(tab_contents_)); + NotificationService::current()->RemoveObserver(this, + NOTIFY_TAB_CONTENTS_DESTROYED, Source<TabContents>(tab_contents_)); + tab_contents_ = NULL; + popup_->Close(); +} + +void DownloadStartedAnimation::AnimateToState(double state) { + if (state >= 1.0) { + Close(); + } else { + Reposition(); + + // Start at zero, peak halfway and end at zero. + double opacity = std::min(1.0 - pow(GetCurrentValue() - 0.5, 2) * 4.0, + static_cast<double>(1.0)); + + popup_->SetLayeredAlpha( + static_cast<BYTE>(opacity * 255.0)); + SchedulePaint(); // Reposition() calls MoveWindow() which never picks up + // alpha changes, so we need to force a paint. + } +} + +void DownloadStartedAnimation::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + Close(); +} diff --git a/chrome/browser/views/download_started_animation.h b/chrome/browser/views/download_started_animation.h new file mode 100644 index 0000000..a0f7099 --- /dev/null +++ b/chrome/browser/views/download_started_animation.h @@ -0,0 +1,85 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_DOWNLOAD_STARTED_ANIMATION_H__ +#define CHROME_BROWSER_VIEWS_DOWNLOAD_STARTED_ANIMATION_H__ + +#include "base/gfx/rect.h" +#include "chrome/common/animation.h" +#include "chrome/common/notification_service.h" +#include "chrome/views/image_view.h" + +namespace ChromeViews { + class HWNDViewContainer; +}; +class TabContents; + +// DownloadStartAnimation creates an animation (which begins running +// immediately) that animates an image downward from the center of the frame +// provided on the constructor, while simultaneously fading it out. To use, +// simply call "new DownloadStartAnimation"; the class cleans itself up when it +// finishes animating. +class DownloadStartedAnimation : public Animation, + public NotificationObserver, + public ChromeViews::ImageView { + public: + DownloadStartedAnimation(TabContents* tab_contents); + + private: + // Move the animation to wherever it should currently be. + void Reposition(); + + // Shut down the animation cleanly. + void Close(); + + // Animation + virtual void AnimateToState(double state); + + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // We use a HWND for the popup so that it may float above any HWNDs in our UI. + ChromeViews::HWNDViewContainer* popup_; + + // The content area holding us. + TabContents* tab_contents_; + + // The content area at the start of the animation. We store this so that the + // download shelf's resizing of the content area doesn't cause the animation + // to move around. This means that once started, the animation won't move + // with the parent window, but it's so fast that this shouldn't cause too + // much heartbreak. + gfx::Rect tab_contents_bounds_; + + DISALLOW_EVIL_CONSTRUCTORS(DownloadStartedAnimation); +}; + +#endif // CHROME_BROWSER_VIEWS_DOWNLOAD_STARTED_ANIMATION_H__ diff --git a/chrome/browser/views/edit_keyword_controller.cc b/chrome/browser/views/edit_keyword_controller.cc new file mode 100644 index 0000000..413f91c --- /dev/null +++ b/chrome/browser/views/edit_keyword_controller.cc @@ -0,0 +1,392 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/edit_keyword_controller.h" + +#include "base/string_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/browser/url_fixer_upper.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/keyword_editor_view.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/label.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/image_view.h" +#include "chrome/views/table_view.h" +#include "chrome/views/window.h" +#include "googleurl/src/gurl.h" + +#include "generated_resources.h" + +using ChromeViews::GridLayout; +using ChromeViews::ImageView; +using ChromeViews::TextField; + + +namespace { +// Converts a URL as understood by TemplateURL to one appropriate for display +// to the user. +std::wstring GetDisplayURL(const TemplateURL& turl) { + return turl.url() ? turl.url()->DisplayURL() : std::wstring(); +} +} // namespace + +EditKeywordController::EditKeywordController( + HWND parent, + const TemplateURL* template_url, + KeywordEditorView* keyword_editor_view, + Profile* profile) + : parent_(parent), + template_url_(template_url), + keyword_editor_view_(keyword_editor_view), + profile_(profile) { + DCHECK(profile_); + Init(); +} + +void EditKeywordController::Show() { + // Window interprets an empty rectangle as needing to query the content for + // the size as well as centering relative to the parent. + window_ = ChromeViews::Window::CreateChromeWindow( + ::IsWindow(parent_) ? parent_ : NULL, + gfx::Rect(), + view_, + this); + window_->Show(); + window_->UpdateDialogButtons(); + title_tf_->SelectAll(); + title_tf_->RequestFocus(); +} + +bool EditKeywordController::IsModal() const { + // If we were called without a KeywordEditorView, and our associated + // window happens to have gone away while the TemplateURLFetcher was + // loading, we might not have a valid parent anymore. + // ::IsWindow() returns a BOOL, which is a typedef for an int and causes a + // warning if we try to return it or cast it as a bool. + if (::IsWindow(parent_)) + return true; + return false; +} + +std::wstring EditKeywordController::GetWindowTitle() const { + return l10n_util::GetString(template_url_ ? + IDS_SEARCH_ENGINES_EDITOR_EDIT_WINDOW_TITLE : + IDS_SEARCH_ENGINES_EDITOR_NEW_WINDOW_TITLE); +} + +int EditKeywordController::GetDialogButtons() const { + return DIALOGBUTTON_OK | DIALOGBUTTON_CANCEL; +} + +bool EditKeywordController::IsDialogButtonEnabled(DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + return (IsKeywordValid() && !title_tf_->GetText().empty() && IsURLValid()); + } + return true; +} + +void EditKeywordController::WindowClosing() { + // User canceled the save, delete us. + delete this; +} + +bool EditKeywordController::Cancel() { + CleanUpCancelledAdd(); + return true; +} + +bool EditKeywordController::Accept() { + std::wstring url_string = GetURL(); + DCHECK(!url_string.empty()); + const std::wstring& keyword = keyword_tf_->GetText(); + + const TemplateURL* existing = + profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword); + if (existing && + (!keyword_editor_view_ || existing != template_url_)) { + // An entry may have been added with the same keyword string while the + // user edited the dialog, either automatically or by the user (if we're + // confirming a JS addition, they could have the Options dialog open at the + // same time). If so, just ignore this add. + // TODO(pamg): Really, we should modify the entry so this later one + // overwrites it. But we don't expect this case to be common. + CleanUpCancelledAdd(); + return true; + } + + if (!keyword_editor_view_) { + // Confiming an entry we got from JS. We have a template_url_, but it + // hasn't yet been added to the model. + DCHECK(template_url_); + // const_cast is ugly, but this is the same thing the TemplateURLModel + // does in a similar situation (updating an existing TemplateURL with + // data from a new one). + TemplateURL* modifiable_url = const_cast<TemplateURL*>(template_url_); + modifiable_url->set_short_name(title_tf_->GetText()); + modifiable_url->set_keyword(keyword); + modifiable_url->SetURL(url_string, 0, 0); + // TemplateURLModel takes ownership of template_url_. + profile_->GetTemplateURLModel()->Add(modifiable_url); + UserMetrics::RecordAction(L"KeywordEditor_AddKeywordJS", profile_); + } else if (!template_url_) { + // Adding a new entry via the KeywordEditorView. + DCHECK(keyword_editor_view_); + if (keyword_editor_view_) + keyword_editor_view_->AddTemplateURL(title_tf_->GetText(), + keyword_tf_->GetText(), + url_string); + } else { + // Modifying an entry via the KeywordEditorView. + DCHECK(keyword_editor_view_); + if (keyword_editor_view_) { + keyword_editor_view_->ModifyTemplateURL(template_url_, + title_tf_->GetText(), + keyword_tf_->GetText(), + url_string); + } + } + return true; +} + +void EditKeywordController::ContentsChanged(TextField* sender, + const std::wstring& new_contents) { + window_->UpdateDialogButtons(); + UpdateImageViews(); +} + +void EditKeywordController::HandleKeystroke(TextField* sender, + UINT message, + TCHAR key, + UINT repeat_count, + UINT flags) { +} + +void EditKeywordController::Init() { + // Create the views we'll need. + view_ = new ChromeViews::View(); + if (template_url_) { + title_tf_ = CreateTextField(template_url_->short_name()); + keyword_tf_ = CreateTextField(template_url_->keyword()); + url_tf_ = CreateTextField(GetDisplayURL(*template_url_)); + // We don't allow users to edit prepopulate URLs. This is done as + // occasionally we need to update the URL of prepopulated TemplateURLs. + url_tf_->SetReadOnly(template_url_->prepopulate_id() != 0); + } else { + title_tf_ = CreateTextField(std::wstring()); + keyword_tf_ = CreateTextField(std::wstring()); + url_tf_ = CreateTextField(std::wstring()); + } + title_iv_ = new ImageView(); + keyword_iv_ = new ImageView(); + url_iv_ = new ImageView(); + + UpdateImageViews(); + + const int related_x = kRelatedControlHorizontalSpacing; + const int related_y = kRelatedControlVerticalSpacing; + const int unrelated_y = kUnrelatedControlVerticalSpacing; + + // View and GridLayout take care of deleting GridLayout for us. + GridLayout* layout = CreatePanelGridLayout(view_); + view_->SetLayoutManager(layout); + + // Define the structure of the layout. + + // For the buttons. + ChromeViews::ColumnSet* column_set = layout->AddColumnSet(0); + column_set->AddPaddingColumn(1, 0); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->LinkColumnSizes(1, 3, -1); + + // For the textfields. + column_set = layout->AddColumnSet(1); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + // For the description. + column_set = layout->AddColumnSet(2); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + // Add the contents. + layout->StartRow(0, 1); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_LABEL)); + layout->AddView(title_tf_); + layout->AddView(title_iv_); + + layout->StartRowWithPadding(0, 1, 0, related_y); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_LABEL)); + layout->AddView(keyword_tf_); + layout->AddView(keyword_iv_); + + layout->StartRowWithPadding(0, 1, 0, related_y); + layout->AddView(CreateLabel(IDS_SEARCH_ENGINES_EDITOR_URL_LABEL)); + layout->AddView(url_tf_); + layout->AddView(url_iv_); + + // On RTL UIs (such as Arabic and Hebrew) the description text is not + // displayed correctly since it contains the substring "%s". This substring + // is not interpreted by the Unicode BiDi algorithm as an LTR string and + // therefore the end result is that the following right to left text is + // displayed: ".three two s% one" (where 'one', 'two', etc. are words in + // Hebrew). + // + // In order to fix this problem we transform the substring "%s" so that it + // is displayed correctly when rendered in an RTL context. + layout->StartRowWithPadding(0, 2, 0, unrelated_y); + std::wstring description = + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_URL_DESCRIPTION_LABEL); + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) { + const std::wstring reversed_percent(L"s%"); + std::wstring::size_type percent_index = + description.find(L"%s", static_cast<std::wstring::size_type>(0)); + if (percent_index != std::wstring::npos) + description.replace(percent_index, + reversed_percent.length(), + reversed_percent); + } + + ChromeViews::Label* description_label = new ChromeViews::Label(description); + description_label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + layout->AddView(description_label); + + layout->AddPaddingRow(0, related_y); +} + +ChromeViews::Label* EditKeywordController::CreateLabel(int message_id) { + ChromeViews::Label* label = new ChromeViews::Label( + l10n_util::GetString(message_id)); + label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + return label; +} + +TextField* EditKeywordController::CreateTextField(const std::wstring& text) { + TextField* text_field = new TextField(); + text_field->SetText(text); + text_field->SetController(this); + return text_field; +} + +bool EditKeywordController::IsURLValid() const { + std::wstring url = GetURL(); + if (url.empty()) + return false; + + // Use TemplateURLRef to extract the search placeholder. + TemplateURLRef template_ref(url, 0, 0); + if (!template_ref.IsValid()) + return false; + + if (template_ref.SupportsReplacement()) { + // If the url has a search term, replace it with a random string and make + // sure the resulting URL is valid. We don't check the validity of the url + // with the search term as that is not necessarily valid. + url = template_ref.ReplaceSearchTerms(TemplateURL(), L"a", + TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()); + } + return GURL(url).is_valid(); +} + +std::wstring EditKeywordController::GetURL() const { + std::wstring url; + TrimWhitespace(TemplateURLRef::DisplayURLToURLRef(url_tf_->GetText()), + TRIM_ALL, &url); + if (url.empty()) + return url; + + // Parse the string as a URL to determine the scheme. If a scheme hasn't been + // specified, we add it. + url_parse::Parsed parts; + std::wstring scheme(URLFixerUpper::SegmentURL(url, &parts)); + if (!parts.scheme.is_valid()) { + std::wstring fixed_scheme(scheme); + fixed_scheme.append(L"://"); + url.insert(0, fixed_scheme); + } + + return url; +} + +bool EditKeywordController::IsKeywordValid() const { + std::wstring keyword = keyword_tf_->GetText(); + if (keyword.empty()) + return true; // Always allow no keyword. + const TemplateURL* turl_with_keyword = + profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword); + return (turl_with_keyword == NULL || turl_with_keyword == template_url_); +} + +void EditKeywordController::UpdateImageViews() { + UpdateImageView(keyword_iv_, IsKeywordValid(), + IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT); + UpdateImageView(url_iv_, IsURLValid(), IDS_SEARCH_ENGINES_INVALID_URL_TT); + UpdateImageView(title_iv_, !title_tf_->GetText().empty(), + IDS_SEARCH_ENGINES_INVALID_TITLE_TT); +} + +void EditKeywordController::UpdateImageView(ImageView* image_view, + bool is_valid, + int invalid_message_id) { + if (is_valid) { + image_view->SetTooltipText(std::wstring()); + image_view->SetImage( + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INPUT_GOOD)); + } else { + image_view->SetTooltipText(l10n_util::GetString(invalid_message_id)); + image_view->SetImage( + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INPUT_NONE)); + } +} + +void EditKeywordController::CleanUpCancelledAdd() { + if (!keyword_editor_view_ && template_url_) { + // When we have no KeywordEditorView, we know that the template_url_ + // hasn't yet been added to the model, so we need to clean it up. + delete template_url_; + template_url_ = NULL; + } +} diff --git a/chrome/browser/views/edit_keyword_controller.h b/chrome/browser/views/edit_keyword_controller.h new file mode 100644 index 0000000..c7e362e --- /dev/null +++ b/chrome/browser/views/edit_keyword_controller.h @@ -0,0 +1,161 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// EditKeywordController provides text fields for editing a keyword: the title, +// url and actual keyword. It is used by the KeywordEditorView of the Options +// dialog, and also on its own to confirm the addition of a keyword added by +// the ExternalJSObject via the RenderView. + +#ifndef CHROME_BROWSER_VIEWS_EDIT_KEYWORD_CONTROLLER_H__ +#define CHROME_BROWSER_VIEWS_EDIT_KEYWORD_CONTROLLER_H__ + +#include <Windows.h> + +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/text_field.h" + +namespace ChromeViews { + class Label; + class ImageView; + class Window; +} + +class KeywordEditorView; +class Profile; +class TemplateURL; +class TemplateURLModel; + +class EditKeywordController : public ChromeViews::TextField::Controller, + public ChromeViews::DialogDelegate { + public: + // The template_url and/or keyword_editor_view may be NULL. + EditKeywordController(HWND parent, + const TemplateURL* template_url, + KeywordEditorView* keyword_editor_view, + Profile* profile); + + virtual ~EditKeywordController() {} + + // Shows the dialog to the user. EditKeywordController takes care of + // deleting itself after show has been invoked. + void Show(); + + // DialogDelegate overrides. + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual int GetDialogButtons() const; + virtual bool IsDialogButtonEnabled(DialogButton button) const; + virtual void WindowClosing(); + virtual bool Cancel(); + virtual bool Accept(); + + // ChromeViews::TextField::Controller overrides. Updates whether the user can + // accept the dialog as well as updating image views showing whether value is + // valid. + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, + TCHAR key, + UINT repeat_count, + UINT flags); + + private: + void Init(); + + // Create a Label containing the text with the specified message id. + ChromeViews::Label* CreateLabel(int message_id); + + // Creates a text field with the specified text. + ChromeViews::TextField* CreateTextField(const std::wstring& text); + + // Returns true if the currently input URL is valid. The URL is valid if it + // contains no search terms and is a valid url, or if it contains a search + // term and replacing that search term with a character results in a valid + // url. + bool IsURLValid() const; + + // Returns the URL the user has input. The returned URL is suitable for use + // by TemplateURL. + std::wstring GetURL() const; + + // Returns whether the currently entered keyword is valid. The keyword is + // valid if it is non-empty and does not conflict with an existing entry. + // NOTE: this is just the keyword, not the title and url. + bool IsKeywordValid() const; + + // Invokes UpdateImageView for each of the images views. + void UpdateImageViews(); + + // Updates the tooltip and image of the image view based on is_valid. If + // is_valid is false the tooltip of the image view is set to the message with + // id invalid_message_id, otherwise the tooltip is set to the empty text. + void UpdateImageView(ChromeViews::ImageView* image_view, + bool is_valid, + int invalid_message_id); + + // Deletes an unused TemplateURL, if its add was cancelled and it's not + // already owned by the TemplateURLModel. + void CleanUpCancelledAdd(); + + // The TemplateURL we're displaying information for. It may be NULL. If we + // have a keyword_editor_view, we assume that this TemplateURL is already in + // the TemplateURLModel; if not, we assume it isn't. + const TemplateURL* template_url_; + + // Used to parent window to. May be NULL or an invalid window. + HWND parent_; + + // View containing the buttons, text fields ... + ChromeViews::View* view_; + + // We may have been created by this, in which case we will call back to it on + // success to add/modify the entry. May be NULL. + KeywordEditorView* keyword_editor_view_; + + // Profile whose TemplateURLModel we're modifying. + Profile* profile_; + + // Container for displaying the contents. + ChromeViews::Window* window_; + + // Text fields. + ChromeViews::TextField* title_tf_; + ChromeViews::TextField* keyword_tf_; + ChromeViews::TextField* url_tf_; + + // Shows error images. + ChromeViews::ImageView* title_iv_; + ChromeViews::ImageView* keyword_iv_; + ChromeViews::ImageView* url_iv_; + + DISALLOW_EVIL_CONSTRUCTORS(EditKeywordController); +}; + +#endif // CHROME_BROWSER_VIEWS_EDIT_KEYWORD_CONTROLLER_H__
\ No newline at end of file diff --git a/chrome/browser/views/event_utils.cc b/chrome/browser/views/event_utils.cc new file mode 100644 index 0000000..75f65c8 --- /dev/null +++ b/chrome/browser/views/event_utils.cc @@ -0,0 +1,56 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/event_utils.h" + +#include "chrome/views/event.h" + +using ChromeViews::Event; + +namespace event_utils { + +WindowOpenDisposition DispositionFromEventFlags(int event_flags) { + if (((event_flags & Event::EF_MIDDLE_BUTTON_DOWN) == + Event::EF_MIDDLE_BUTTON_DOWN) || + ((event_flags & Event::EF_CONTROL_DOWN) == + Event::EF_CONTROL_DOWN)) { + return ((event_flags & Event::EF_SHIFT_DOWN) == Event::EF_SHIFT_DOWN) ? + NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; + } + + if ((event_flags & Event::EF_SHIFT_DOWN) == Event::EF_SHIFT_DOWN) + return NEW_WINDOW; + return false /*event.IsAltDown()*/ ? SAVE_TO_DISK : CURRENT_TAB; +} + +bool IsPossibleDispositionEvent(const ChromeViews::MouseEvent& event) { + return event.IsLeftMouseButton() || event.IsMiddleMouseButton(); +} + +} diff --git a/chrome/browser/views/event_utils.h b/chrome/browser/views/event_utils.h new file mode 100644 index 0000000..710d124 --- /dev/null +++ b/chrome/browser/views/event_utils.h @@ -0,0 +1,52 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_EVENT_UTILS_H__ +#define CHROME_BROWSER_VIEWS_EVENT_UTILS_H__ + +#include "webkit/glue/window_open_disposition.h" + +namespace ChromeViews { +class MouseEvent; +} + +namespace event_utils { + +// Translates event flags into what kind of disposition they represents. +// For example, a middle click would mean to open a background tab. +// event_flags are the flags as understood by ChromeViews::MouseEvent. +WindowOpenDisposition DispositionFromEventFlags(int event_flags); + +// Returns true if the specified mouse event may have a +// WindowOptionDisposition. +bool IsPossibleDispositionEvent(const ChromeViews::MouseEvent& event); + +} + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_BAR_VIEW_H__ diff --git a/chrome/browser/views/first_run_bubble.cc b/chrome/browser/views/first_run_bubble.cc new file mode 100644 index 0000000..86f9fb1 --- /dev/null +++ b/chrome/browser/views/first_run_bubble.cc @@ -0,0 +1,241 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/first_run_bubble.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/browser/options_window.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/event.h" +#include "chrome/views/label.h" +#include "chrome/views/native_button.h" + +#include "generated_resources.h" + +namespace { + +// How much extra padding to put around our content over what +// infobubble provides. +static const int kBubblePadding = 4; + +// TODO(cpu): bug 1187517. It is possible that there is no default provider. +// we should make sure there is none at first run. +std::wstring GetDefaultSearchEngineName() { + Browser* browser = BrowserList::GetLastActive(); + DCHECK(browser); + const TemplateURL* const default_provider = + browser->profile()->GetTemplateURLModel()->GetDefaultSearchProvider(); + DCHECK(default_provider); + return default_provider->short_name(); +} + +} // namespace + +// Implements the client view inside the first run bubble. It is kind of a +// dialog-ish view, but is not a true dialog. +class FirstRunBubbleView : public ChromeViews::View, + public ChromeViews::NativeButton::Listener { + public: + explicit FirstRunBubbleView(FirstRunBubble* bubble_window) + : bubble_window_(bubble_window), + label1_(NULL), + label2_(NULL), + label3_(NULL), + keep_button_(NULL), + change_button_(NULL) { + ChromeFont& font = + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont); + + label1_ = new ChromeViews::Label(l10n_util::GetString(IDS_FR_BUBBLE_TITLE)); + label1_->SetFont(font.DeriveFont(3, ChromeFont::BOLD)); + label1_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + AddChildView(label1_); + + CSize ps; + GetPreferredSize(&ps); + + label2_ = + new ChromeViews::Label(l10n_util::GetString(IDS_FR_BUBBLE_SUBTEXT)); + label2_->SetMultiLine(true); + label2_->SetFont(font); + label2_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label2_->SizeToFit(ps.cx - kBubblePadding * 2); + AddChildView(label2_); + + std::wstring question_str + = l10n_util::GetStringF(IDS_FR_BUBBLE_QUESTION, + GetDefaultSearchEngineName()); + label3_ = new ChromeViews::Label(question_str); + label3_->SetMultiLine(true); + label3_->SetFont(font); + label3_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label3_->SizeToFit(ps.cx - kBubblePadding * 2); + AddChildView(label3_); + + std::wstring keep_str = l10n_util::GetStringF(IDS_FR_BUBBLE_OK, + GetDefaultSearchEngineName()); + keep_button_ = new ChromeViews::NativeButton(keep_str, true); + keep_button_->SetListener(this); + AddChildView(keep_button_); + + std::wstring change_str = l10n_util::GetString(IDS_FR_BUBBLE_CHANGE); + change_button_ = new ChromeViews::NativeButton(change_str); + change_button_->SetListener(this); + AddChildView(change_button_); + } + + // Overridden from ChromeViews::View. + virtual void DidChangeBounds(const CRect& previous, const CRect& current) { + Layout(); + } + + // Overridden from NativeButton::Listener. + virtual void ButtonPressed(ChromeViews::NativeButton* sender) { + bubble_window_->Close(); + if (change_button_ == sender) { + Browser* browser = BrowserList::GetLastActive(); + if (browser) { + ShowOptionsWindow(OPTIONS_PAGE_GENERAL, OPTIONS_GROUP_DEFAULT_SEARCH, + browser->profile()); + } + } + } + + // Overridden from ChromeViews::View. + virtual void Layout() { + CSize canvas; + GetPreferredSize(&canvas); + + CSize pref_size; + label1_->GetPreferredSize(&pref_size); + label1_->SetBounds(kBubblePadding, kBubblePadding, pref_size.cx, + pref_size.cy); + + int next_v_space = label1_->GetY() + label1_->GetHeight() + + kRelatedControlSmallVerticalSpacing; + + label2_->GetPreferredSize(&pref_size); + label2_->SetBounds(kBubblePadding, next_v_space, + canvas.cx - kBubblePadding * 2, + pref_size.cy); + + next_v_space = label2_->GetY() + label2_->GetHeight() + + kPanelSubVerticalSpacing; + + label3_->GetPreferredSize(&pref_size); + label3_->SetBounds(kBubblePadding, next_v_space, + canvas.cx - kBubblePadding * 2, + pref_size.cy); + + change_button_->GetPreferredSize(&pref_size); + change_button_->SetBounds(canvas.cx - pref_size.cx - kBubblePadding, + canvas.cy - pref_size.cy - kButtonVEdgeMargin, + pref_size.cx, pref_size.cy); + + keep_button_->GetPreferredSize(&pref_size); + keep_button_->SetBounds(change_button_->GetX() - pref_size.cx - + kRelatedButtonHSpacing, change_button_->GetY(), + pref_size.cx, pref_size.cy); + } + + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (keep_button_) + keep_button_->RequestFocus(); + } + + // Overridden from ChromeViews::View. + virtual void GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_FIRSTRUNBUBBLE_DIALOG_WIDTH_CHARS, + IDS_FIRSTRUNBUBBLE_DIALOG_HEIGHT_LINES).ToSIZE(); + } + + private: + FirstRunBubble* bubble_window_; + ChromeViews::Label* label1_; + ChromeViews::Label* label2_; + ChromeViews::Label* label3_; + ChromeViews::NativeButton* change_button_; + ChromeViews::NativeButton* keep_button_; + DISALLOW_EVIL_CONSTRUCTORS(FirstRunBubbleView); +}; + +// Keep the bubble around for kLingerTime milliseconds, to prevent accidental +// closure. +static const int kLingerTime = 1000; + +void FirstRunBubble::OnActivate(UINT action, BOOL minimized, HWND window) { + // We might get re-enabled right before we are closed (sequence is: we get + // deactivated, we call close, before we are actually closed we get + // reactivated). Don't do the disabling of the parent in such cases. + if (action == WA_ACTIVE && !has_been_activated_) { + has_been_activated_ = true; + + // Disable the browser to prevent accidental rapid clicks from closing the + // bubble. + ::EnableWindow(GetParent(), false); + + MessageLoop::current()->PostDelayedTask(FROM_HERE, + enable_window_method_factory_.NewRunnableMethod( + &FirstRunBubble::EnableParent), + kLingerTime); + } + InfoBubble::OnActivate(action, minimized, window); +} + +void FirstRunBubble::InfoBubbleClosing(InfoBubble* info_bubble) { + // Make sure our parent window is re-enabled. + if (!IsWindowEnabled(GetParent())) + ::EnableWindow(GetParent(), true); + enable_window_method_factory_.RevokeAll(); +} + +FirstRunBubble* FirstRunBubble::Show(HWND parent_hwnd, + const gfx::Rect& position_relative_to) { + FirstRunBubble* window = new FirstRunBubble(); + ChromeViews::View* view = new FirstRunBubbleView(window); + window->SetDelegate(window); + window->Init(parent_hwnd, position_relative_to, view); + ChromeFrame* frame = window->GetHostingFrame(); + DCHECK(frame); + frame->InfoBubbleShowing(); + window->ShowWindow(SW_SHOW); + return window; +} + +void FirstRunBubble::EnableParent() { + ::EnableWindow(GetParent(), true); +} diff --git a/chrome/browser/views/first_run_bubble.h b/chrome/browser/views/first_run_bubble.h new file mode 100644 index 0000000..b904147 --- /dev/null +++ b/chrome/browser/views/first_run_bubble.h @@ -0,0 +1,72 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_FIRST_RUN_BUBBLE_H__ +#define CHROME_BROWSER_VIEWS_FIRST_RUN_BUBBLE_H__ + +#include "base/task.h" +#include "chrome/browser/views/info_bubble.h" + +class FirstRunBubble : public InfoBubble, + public InfoBubbleDelegate { + public: + static FirstRunBubble* Show(HWND parent_hwnd, + const gfx::Rect& position_relative_to); + + FirstRunBubble() + : enable_window_method_factory_(this), + has_been_activated_(false) { + } + + virtual ~FirstRunBubble() { + // We should have called RevokeAll on the method factory already. + DCHECK(enable_window_method_factory_.empty()); + enable_window_method_factory_.RevokeAll(); + } + + // Overridden from InfoBubble: + virtual void OnActivate(UINT action, BOOL minimized, HWND window); + + // InfoBubbleDelegate. + virtual void InfoBubbleClosing(InfoBubble* info_bubble); + virtual bool CloseOnEscape() { return true; } + + private: + // Re-enable the parent window. + void EnableParent(); + + // Whether we have already been activated. + bool has_been_activated_; + + ScopedRunnableMethodFactory<FirstRunBubble> enable_window_method_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(FirstRunBubble); +}; + +#endif // CHROME_BROWSER_VIEWS_FIRST_RUN_BUBBLE_H__ diff --git a/chrome/browser/views/first_run_customize_view.cc b/chrome/browser/views/first_run_customize_view.cc new file mode 100644 index 0000000..6b8263a --- /dev/null +++ b/chrome/browser/views/first_run_customize_view.cc @@ -0,0 +1,253 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/first_run_customize_view.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/importer.h" +#include "chrome/browser/first_run.h" +#include "chrome/browser/shell_integration.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/throbber.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +FirstRunCustomizeView::FirstRunCustomizeView(Profile* profile, + ImporterHost* importer_host) + : FirstRunViewBase(profile), + main_label_(NULL), + import_cbox_(NULL), + default_browser_cbox_(NULL), + import_from_combo_(NULL), + shortcuts_label_(NULL), + desktop_shortcut_cbox_(NULL), + quick_shortcut_cbox_(NULL), + customize_observer_(NULL) { + importer_host_ = importer_host; + DCHECK(importer_host_); + SetupControls(); +} + +FirstRunCustomizeView::~FirstRunCustomizeView() { +} + +ChromeViews::CheckBox* FirstRunCustomizeView::MakeCheckBox(int label_id) { + ChromeViews::CheckBox* cbox = + new ChromeViews::CheckBox(l10n_util::GetString(label_id)); + cbox->SetListener(this); + AddChildView(cbox); + return cbox; +} + +void FirstRunCustomizeView::SetupControls() { + using ChromeViews::Label; + using ChromeViews::CheckBox; + + main_label_ = new Label(l10n_util::GetString(IDS_FR_CUSTOMIZE_DLG_TEXT)); + main_label_->SetMultiLine(true); + main_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(main_label_); + + import_cbox_ = MakeCheckBox(IDS_FR_CUSTOMIZE_IMPORT); + + import_from_combo_ = new ChromeViews::ComboBox(this); + AddChildView(import_from_combo_); + + default_browser_cbox_ = MakeCheckBox(IDS_FR_CUSTOMIZE_DEFAULT_BROWSER); + + shortcuts_label_ = + new Label(l10n_util::GetString(IDS_FR_CUSTOMIZE_SHORTCUTS)); + shortcuts_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(shortcuts_label_); + + // The two check boxes for the different shortcut creation. + desktop_shortcut_cbox_ = MakeCheckBox(IDS_FR_CUSTOM_SHORTCUT_DESKTOP); + desktop_shortcut_cbox_->SetIsSelected(true); + + quick_shortcut_cbox_ = MakeCheckBox(IDS_FR_CUSTOM_SHORTCUT_QUICKL); + quick_shortcut_cbox_->SetIsSelected(true); +} + +void FirstRunCustomizeView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_FIRSTRUNCUSTOMIZE_DIALOG_WIDTH_CHARS, + IDS_FIRSTRUNCUSTOMIZE_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void FirstRunCustomizeView::Layout() { + FirstRunViewBase::Layout(); + + const int kVertSpacing = 8; + const int kComboExtraPad = 8; + + CSize canvas; + GetPreferredSize(&canvas); + + // Welcome label goes in to to the left. It does not go across the + // entire window because the background gets busy on the right. + CSize pref_size; + main_label_->GetPreferredSize(&pref_size); + main_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin, + canvas.cx - pref_size.cx, pref_size.cy); + AdjustDialogWidth(main_label_); + + int next_v_space = background_image()->GetY() + + background_image()->GetHeight() + kPanelVertMargin; + + import_cbox_->GetPreferredSize(&pref_size); + import_cbox_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + + import_cbox_->SetIsSelected(true); + + int x_offset = import_cbox_->GetX() + + import_cbox_->GetWidth(); + + import_from_combo_->GetPreferredSize(&pref_size); + import_from_combo_->SetBounds(x_offset, next_v_space, + pref_size.cx + kComboExtraPad, pref_size.cy); + + AdjustDialogWidth(import_from_combo_); + + next_v_space = import_cbox_->GetY() + import_cbox_->GetHeight() + + kUnrelatedControlVerticalSpacing; + + default_browser_cbox_->GetPreferredSize(&pref_size); + default_browser_cbox_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + + AdjustDialogWidth(default_browser_cbox_); + + next_v_space += default_browser_cbox_->GetHeight() + + kUnrelatedControlVerticalSpacing; + + shortcuts_label_->GetPreferredSize(&pref_size); + shortcuts_label_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + + AdjustDialogWidth(shortcuts_label_); + + next_v_space += shortcuts_label_->GetHeight() + + kRelatedControlVerticalSpacing; + + desktop_shortcut_cbox_->GetPreferredSize(&pref_size); + desktop_shortcut_cbox_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + + AdjustDialogWidth(desktop_shortcut_cbox_); + + next_v_space += desktop_shortcut_cbox_->GetHeight() + + kRelatedControlVerticalSpacing; + + quick_shortcut_cbox_->GetPreferredSize(&pref_size); + quick_shortcut_cbox_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + + AdjustDialogWidth(quick_shortcut_cbox_); +} + +void FirstRunCustomizeView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (import_cbox_ == sender) { + // Disable the import combobox if the user unchecks the checkbox. + import_from_combo_->SetEnabled(import_cbox_->IsSelected()); + } +} + +int FirstRunCustomizeView::GetItemCount(ChromeViews::ComboBox* source) { + return importer_host_->GetAvailableProfileCount(); +} + +std::wstring FirstRunCustomizeView::GetItemAt(ChromeViews::ComboBox* source, + int index) { + return importer_host_->GetSourceProfileNameAt(index); +} + +std::wstring FirstRunCustomizeView::GetWindowTitle() const { + return l10n_util::GetString(IDS_FR_CUSTOMIZE_DLG_TITLE); +} + +bool FirstRunCustomizeView::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) + return false; + + DisableButtons(); + import_cbox_->SetEnabled(false); + import_from_combo_->SetEnabled(false); + default_browser_cbox_->SetEnabled(false); + desktop_shortcut_cbox_->SetEnabled(false); + quick_shortcut_cbox_->SetEnabled(false); + + if (desktop_shortcut_cbox_->IsSelected()) { + UserMetrics::RecordAction(L"FirstRunCustom_Do_DesktopShortcut", profile_); + CreateDesktopShortcut(); + } + if (quick_shortcut_cbox_->IsSelected()) { + UserMetrics::RecordAction(L"FirstRunCustom_Do_QuickLShortcut", profile_); + CreateQuickLaunchShortcut(); + } + if (!import_cbox_->IsSelected()) { + UserMetrics::RecordAction(L"FirstRunCustom_No_Import", profile_); + } else { + int browser_selected = import_from_combo_->GetSelectedItem(); + FirstRun::ImportSettings(profile_, browser_selected, + GetDefaultImportItems(), dialog_->GetHWND()); + } + if (default_browser_cbox_->IsSelected()) { + UserMetrics::RecordAction(L"FirstRunCustom_Do_DefBrowser", profile_); + ShellIntegration::SetAsDefaultBrowser(); + } + + if (customize_observer_) + customize_observer_->CustomizeAccepted(); + + // Exit the message loop we were started with so that startup can continue. + MessageLoop::current()->Quit(); + + return true; +} + +bool FirstRunCustomizeView::Cancel() { + if (customize_observer_) + customize_observer_->CustomizeCanceled(); + + // Don't quit the message loop in this case - we're still showing the main + // First run dialog box underneath ourselves. + + return true; +} diff --git a/chrome/browser/views/first_run_customize_view.h b/chrome/browser/views/first_run_customize_view.h new file mode 100644 index 0000000..4a22cb6 --- /dev/null +++ b/chrome/browser/views/first_run_customize_view.h @@ -0,0 +1,120 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_FIRST_RUN_CUSTOMIZE_VIEW_H_ +#define CHROME_BROWSER_VIEWS_FIRST_RUN_CUSTOMIZE_VIEW_H_ + +#include "chrome/browser/views/first_run_view_base.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class Label; +class Window; +class ImageView; +class Separator; +class CheckBox; +class ComboBox; + +} + +class Profile; + +// FirstRunCustomizeView implements the dialog that allows the user to do +// some simple customizations during the first run. +class FirstRunCustomizeView : public FirstRunViewBase, + public ChromeViews::NativeButton::Listener, + public ChromeViews::ComboBox::Model { + public: + class CustomizeViewObserver { + public: + // Called when the user has accepted the dialog. + virtual void CustomizeAccepted() = 0; + // Called when the user has canceled the dialog. + virtual void CustomizeCanceled() = 0; + }; + + FirstRunCustomizeView(Profile* profile, ImporterHost* importer_host); + virtual ~FirstRunCustomizeView(); + + void set_dialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + + // Sets the object that receives UserAccept and UserCancel notifications. + void set_observer(CustomizeViewObserver* observer) { + customize_observer_ = observer; + }; + + // Overridden from ChromeViews::View. + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + // Overridden from ChromeViews::DialogDelegate. + virtual bool Accept(); + virtual bool Cancel(); + + // Overridden form ChromeViews::NativeButton::Listener. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Overridden form ChromeViews::ComboBox::Model. + virtual int GetItemCount(ChromeViews::ComboBox* source); + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Overridden from ChromeViews::WindowDelegate. + virtual std::wstring GetWindowTitle() const; + + // Yes, we're modal. + // NOTE: if you change this you'll need to make sure it isn't possible to + // close the window while importing. + virtual bool IsModal() const { + return true; + } + + private: + // Initializes the controls on the dialog. + void SetupControls(); + + ChromeViews::CheckBox* MakeCheckBox(int resource_id); + + ChromeViews::Label* main_label_; + ChromeViews::CheckBox* import_cbox_; + ChromeViews::CheckBox* default_browser_cbox_; + ChromeViews::ComboBox* import_from_combo_; + ChromeViews::Label* shortcuts_label_; + ChromeViews::CheckBox* desktop_shortcut_cbox_; + ChromeViews::CheckBox* quick_shortcut_cbox_; + + CustomizeViewObserver* customize_observer_; + + DISALLOW_EVIL_CONSTRUCTORS(FirstRunCustomizeView); +}; + +#endif // CHROME_BROWSER_VIEWS_FIRST_RUN_CUSTOMIZE_VIEW_H_ diff --git a/chrome/browser/views/first_run_view.cc b/chrome/browser/views/first_run_view.cc new file mode 100644 index 0000000..1beed44 --- /dev/null +++ b/chrome/browser/views/first_run_view.cc @@ -0,0 +1,233 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/first_run_view.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/importer.h" +#include "chrome/browser/first_run.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/first_run_customize_view.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/throbber.h" +#include "chrome/views/separator.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +namespace { + +// Adds a bullet glyph to a string. +std::wstring AddBullet(const std::wstring& text) { + std::wstring btext(L" " + text); + return btext.insert(0, 1, L'\u2022'); +} + +} // namespace + +FirstRunView::FirstRunView(Profile* profile) + : FirstRunViewBase(profile), + welcome_label_(NULL), + actions_label_(NULL), + actions_import_(NULL), + actions_shorcuts_(NULL), + customize_link_(NULL), + customize_selected_(false) { + importer_host_ = new ImporterHost(); + SetupControls(); +} + +FirstRunView::~FirstRunView() { + FirstRunComplete(); + + // Exit the message loop we were started with so that startup can continue. + MessageLoop::current()->Quit(); +} + +void FirstRunView::SetupControls() { + using ChromeViews::Label; + using ChromeViews::Link; + + welcome_label_ = new Label(l10n_util::GetString(IDS_FIRSTRUN_DLG_TEXT)); + welcome_label_->SetMultiLine(true); + welcome_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + welcome_label_->SizeToFit(0); + AddChildView(welcome_label_); + + actions_label_ = new Label(l10n_util::GetString(IDS_FIRSTRUN_DLG_DETAIL)); + actions_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(actions_label_); + + // The first action label will tell what we are going to import from which + // browser, which we obtain from the ImporterHost. We need that the first + // browser profile be the default browser. + std::wstring label1; + if (importer_host_->GetAvailableProfileCount() > 0) { + label1 = l10n_util::GetStringF(IDS_FIRSTRUN_DLG_ACTION1, + importer_host_->GetSourceProfileNameAt(0)); + } else { + NOTREACHED(); + } + + actions_import_ = new Label(AddBullet(label1)); + actions_import_->SetMultiLine(true); + actions_import_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(actions_import_); + std::wstring label2 = l10n_util::GetString(IDS_FIRSTRUN_DLG_ACTION2); + actions_shorcuts_ = new Label(AddBullet(label2)); + actions_shorcuts_->SetHorizontalAlignment(Label::ALIGN_LEFT); + actions_shorcuts_->SetMultiLine(true); + AddChildView(actions_shorcuts_); + + customize_link_ = new Link(l10n_util::GetString(IDS_FIRSTRUN_DLG_OVERRIDE)); + customize_link_->SetController(this); + AddChildView(customize_link_); +} + +void FirstRunView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_FIRSTRUN_DIALOG_WIDTH_CHARS, + IDS_FIRSTRUN_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void FirstRunView::Layout() { + FirstRunViewBase::Layout(); + + const int kVertSpacing = 8; + + CSize pref_size; + welcome_label_->GetPreferredSize(&pref_size); + welcome_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin, + pref_size.cx, pref_size.cy); + AdjustDialogWidth(welcome_label_); + + int next_v_space = background_image()->GetY() + + background_image()->GetHeight() + kPanelVertMargin; + + actions_label_->GetPreferredSize(&pref_size); + actions_label_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + AdjustDialogWidth(actions_label_); + + next_v_space = actions_label_->GetY() + + actions_label_->GetHeight() + kVertSpacing; + + + // First give the label some width, so that GetPreferredSize can return us a + // reasonable height... + actions_import_->SetBounds(0, 0, GetWidth() - kPanelHorizMargin, 0); + actions_import_->GetPreferredSize(&pref_size); + actions_import_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx + 100, pref_size.cy); + + next_v_space = actions_import_->GetY() + + actions_import_->GetHeight() + kVertSpacing; + AdjustDialogWidth(actions_import_); + + actions_shorcuts_->SetBounds(0, 0, GetWidth() - kPanelHorizMargin, 0); + actions_shorcuts_->GetPreferredSize(&pref_size); + actions_shorcuts_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); + AdjustDialogWidth(actions_shorcuts_); + + next_v_space = actions_shorcuts_->GetY() + + actions_shorcuts_->GetHeight() + + kUnrelatedControlVerticalSpacing; + + customize_link_->GetPreferredSize(&pref_size); + customize_link_->SetBounds(kPanelHorizMargin, next_v_space, + pref_size.cx, pref_size.cy); +} + +std::wstring FirstRunView::GetDialogButtonLabel(DialogButton button) const { + if (DIALOGBUTTON_OK == button) + return l10n_util::GetString(IDS_FIRSTRUN_DLG_OK); + // The other buttons get the default text. + return std::wstring(); +} + +void FirstRunView::OpenCustomizeDialog() { + // The customize dialog now owns the importer host object. + FirstRunCustomizeView* customize_view = + new FirstRunCustomizeView(profile_, importer_host_); + + ChromeViews::Window* customize_dialog = + ChromeViews::Window::CreateChromeWindow(dialog_->GetHWND(), + gfx::Rect(), customize_view, + customize_view); + customize_dialog->Show(); + customize_view->set_dialog(customize_dialog); + customize_view->set_observer(this); +} + +void FirstRunView::LinkActivated(ChromeViews::Link* source, int event_flags) { + OpenCustomizeDialog(); +} + +std::wstring FirstRunView::GetWindowTitle() const { + return l10n_util::GetString(IDS_FIRSTRUN_DLG_TITLE); +} + +bool FirstRunView::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) + return false; + + DisableButtons(); + customize_link_->SetEnabled(false); + CreateDesktopShortcut(); + CreateQuickLaunchShortcut(); + // Index 0 is the default browser. + FirstRun::ImportSettings(profile_, 0, GetDefaultImportItems(), + dialog_->GetHWND()); + UserMetrics::RecordAction(L"FirstRunDef_Accept", profile_); + + return true; +} + +bool FirstRunView::Cancel() { + UserMetrics::RecordAction(L"FirstRunDef_Cancel", profile_); + return true; +} + +// Notification from the customize dialog that the user accepted. Since all +// the work is done there we got nothing else to do. +void FirstRunView::CustomizeAccepted() { + dialog_->Close(); +} + +// Notification from the customize dialog that the user cancelled. +void FirstRunView::CustomizeCanceled() { + UserMetrics::RecordAction(L"FirstRunCustom_Cancel", profile_); +} diff --git a/chrome/browser/views/first_run_view.h b/chrome/browser/views/first_run_view.h new file mode 100644 index 0000000..47e3645 --- /dev/null +++ b/chrome/browser/views/first_run_view.h @@ -0,0 +1,94 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_H__ +#define CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_H__ + +#include "chrome/browser/views/first_run_view_base.h" +#include "chrome/browser/views/first_run_customize_view.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/link.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class Label; +class Window; + +} + +class Profile; +class ImporterHost; + +// FirstRunView implements the dialog that welcomes to user to Chrome after +// a fresh install. +class FirstRunView : public FirstRunViewBase, + public ChromeViews::LinkController, + public FirstRunCustomizeView::CustomizeViewObserver { + public: + explicit FirstRunView(Profile* profile); + virtual ~FirstRunView(); + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + // Overridden from ChromeViews::DialogDelegate: + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool Accept(); + virtual bool Cancel(); + + // Overridden from ChromeViews::WindowDelegate: + virtual std::wstring GetWindowTitle() const; + + // Overridden from ChromeViews::LinkActivated: + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + // Overridden from FirstRunCustomizeView: + virtual void CustomizeAccepted(); + virtual void CustomizeCanceled(); + + private: + // Initializes the controls on the dialog. + void SetupControls(); + + // Creates the dialog that allows the user to customize work items. + void OpenCustomizeDialog(); + + ChromeViews::Label* welcome_label_; + ChromeViews::Label* actions_label_; + ChromeViews::Label* actions_import_; + ChromeViews::Label* actions_shorcuts_; + ChromeViews::Link* customize_link_; + bool customize_selected_; + + DISALLOW_EVIL_CONSTRUCTORS(FirstRunView); +}; + +#endif // CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_H__ diff --git a/chrome/browser/views/first_run_view_base.cc b/chrome/browser/views/first_run_view_base.cc new file mode 100644 index 0000000..b9c04fe --- /dev/null +++ b/chrome/browser/views/first_run_view_base.cc @@ -0,0 +1,204 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/first_run_view_base.h" + +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/first_run.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" +#include "chrome/views/client_view.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/throbber.h" +#include "chrome/views/separator.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +FirstRunViewBase::FirstRunViewBase(Profile* profile) + : dialog_(NULL), + preferred_width_(0), + background_image_(NULL), + separator_1_(NULL), + separator_2_(NULL), + importer_host_(NULL), + profile_(profile) { + DCHECK(profile); + SetupControls(); +} + +FirstRunViewBase::~FirstRunViewBase() { + // Register and set the "show first run information bubble" state so that the + // browser can read it later. + PrefService* local_state = g_browser_process->local_state(); + if (!local_state->IsPrefRegistered(prefs::kShouldShowFirstRunBubble)) { + local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false); + local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, true); + } + + if (!local_state->IsPrefRegistered(prefs::kShouldShowWelcomePage)) { + local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false); + local_state->SetBoolean(prefs::kShouldShowWelcomePage, true); + } +} + +void FirstRunViewBase::SetupControls() { + using ChromeViews::Label; + using ChromeViews::ImageView; + using ChromeViews::Background; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + background_image_ = new ChromeViews::ImageView(); + background_image_->SetImage(rb.GetBitmapNamed(IDR_WIZARD_ICON)); + background_image_->SetHorizontalAlignment(ImageView::TRAILING); + + int color = 0; + { + SkAutoLockPixels pixel_loc(background_image_->GetImage()); + uint32_t* pixel = background_image_->GetImage().getAddr32(0, 0); + color = (0xff & (*pixel)); + } + Background* bkg = Background::CreateSolidBackground(color, color, color); + + // The bitmap we use as the background contains a clipped logo and therefore + // we can not automatically mirror it for RTL UIs by simply flipping it. This + // is why we load a different bitmap if the View is using a right-to-left UI + // layout. + // + // Note that we first load the LTR image and then replace it with the RTL + // image because the code above derives the background color from the LTR + // image so we have to use the LTR logo initially and then replace it with + // the RTL logo if we find out that we are running in a right-to-left locale. + if (UILayoutIsRightToLeft()) + background_image_->SetImage(rb.GetBitmapNamed(IDR_WIZARD_ICON_RTL)); + + background_image_->SetBackground(bkg); + AddChildView(background_image_); + + // The first separator marks the end of the image. + separator_1_ = new ChromeViews::Separator; + AddChildView(separator_1_); + + // The second separator marks the start of buttons. + separator_2_ = new ChromeViews::Separator; + AddChildView(separator_2_); +} + +void FirstRunViewBase::AdjustDialogWidth(const ChromeViews::View* sub_view) { + CRect bounds; + sub_view->GetBounds(&bounds); + preferred_width_ = + std::max(preferred_width_, + static_cast<int>(bounds.right) + kPanelHorizMargin); +} + +void FirstRunViewBase::SetMinimumDialogWidth(int width) { + preferred_width_ = std::max(preferred_width_, width); +} + +void FirstRunViewBase::Layout() { + const int kVertSpacing = 8; + + CSize canvas; + GetPreferredSize(&canvas); + + CSize pref_size; + background_image_->GetPreferredSize(&pref_size); + background_image_->SetBounds(0, 0, canvas.cx, pref_size.cy); + + int next_v_space = background_image_->GetY() + + background_image_->GetHeight() - 2; + + separator_1_->GetPreferredSize(&pref_size); + separator_1_->SetBounds(0, next_v_space, + canvas.cx + 1, + pref_size.cy); + + next_v_space = canvas.cy - kPanelSubVerticalSpacing; + separator_2_->GetPreferredSize(&pref_size); + separator_2_->SetBounds(kPanelHorizMargin , next_v_space, + canvas.cx - 2*kPanelHorizMargin, pref_size.cy); +} + +bool FirstRunViewBase::CanResize() const { + return false; +} + +bool FirstRunViewBase::CanMaximize() const { + return false; +} + +bool FirstRunViewBase::IsAlwaysOnTop() const { + return false; +} + +bool FirstRunViewBase::HasAlwaysOnTopMenu() const { + return false; +} + +int FirstRunViewBase::GetDefaultImportItems() const { + // It is best to avoid importing cookies because there is a bug that make + // the process take way too much time among other issues. So for the time + // being we say: TODO(CPU): Bug 1196875 + return HISTORY | FAVORITES | PASSWORDS | SEARCH_ENGINES | HOME_PAGE; +}; + +void FirstRunViewBase::DisableButtons() { + dialog_->EnableClose(false); + ChromeViews::ClientView* cv = + reinterpret_cast<ChromeViews::ClientView*>(GetParent()); + if (cv) { + cv->ok_button()->SetEnabled(false); + cv->cancel_button()->SetEnabled(false); + } +} + +bool FirstRunViewBase::CreateDesktopShortcut() { + return FirstRun::CreateChromeDesktopShortcut(); +} + +bool FirstRunViewBase::CreateQuickLaunchShortcut() { + return FirstRun::CreateChromeQuickLaunchShortcut(); +} + +bool FirstRunViewBase::FirstRunComplete() { + return FirstRun::CreateSentinel(); +} diff --git a/chrome/browser/views/first_run_view_base.h b/chrome/browser/views/first_run_view_base.h new file mode 100644 index 0000000..9266ce8 --- /dev/null +++ b/chrome/browser/views/first_run_view_base.h @@ -0,0 +1,115 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_BASE_H__ +#define CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_BASE_H__ +#include "chrome/browser/importer.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class Window; +class ImageView; +class Separator; +class Throbber; + +} + +class Profile; +class ImporterHost; + +// This class abstracts the code that creates the dialog look for the two +// first-run dialogs. This amounts to the bitmap, the two separators, the +// progress throbber and some common resize code. +class FirstRunViewBase : public ChromeViews::View, + public ChromeViews::DialogDelegate { + public: + explicit FirstRunViewBase(Profile* profile); + virtual ~FirstRunViewBase(); + + // Sets the dialog window that host the view. + void set_dialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + + // Overridden from ChromeViews::View. + virtual void Layout(); + + // Overridden from ChromeViews::WindowDelegate. + virtual bool CanResize() const; + virtual bool CanMaximize() const; + virtual bool IsAlwaysOnTop() const; + virtual bool HasAlwaysOnTopMenu() const; + + protected: + // Returns the items that the first run process is required to import + // from other browsers. + int GetDefaultImportItems() const; + + // Creates the desktop and quick launch shortcut. Existing shortcut is lost. + bool CreateDesktopShortcut(); + bool CreateQuickLaunchShortcut(); + + // Modifies the chrome configuration so that the first-run dialogs are not + // shown again. + bool FirstRunComplete(); + + // Disables the standard buttons of the dialog. Useful when importing. + void DisableButtons(); + // Computes a tight dialog width given a contained UI element. + void AdjustDialogWidth(const ChromeViews::View* sub_view); + + // Sets a minimum dialog size. + void SetMinimumDialogWidth(int width); + + // Returns the background image. It is useful for getting the metrics. + const ChromeViews::ImageView* background_image() const { + return background_image_; + } + // Returns the computed preferred width of the dialog. This value can change + // when AdjustDialogWidth() is called during layout. + int preferred_width() const { + return preferred_width_; + } + + scoped_refptr<ImporterHost> importer_host_; + Profile* profile_; + ChromeViews::Window* dialog_; + + private: + // Initializes the controls on the dialog. + void SetupControls(); + ChromeViews::ImageView* background_image_; + ChromeViews::Separator* separator_1_; + ChromeViews::Separator* separator_2_; + int preferred_width_; + + DISALLOW_EVIL_CONSTRUCTORS(FirstRunViewBase); +}; + +#endif // CHROME_BROWSER_VIEWS_FIRST_RUN_VIEW_BASE_H__ diff --git a/chrome/browser/views/go_button.cc b/chrome/browser/views/go_button.cc new file mode 100644 index 0000000..8319be9 --- /dev/null +++ b/chrome/browser/views/go_button.cc @@ -0,0 +1,158 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/go_button.h" + +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +GoButton::GoButton(LocationBarView* location_bar, + CommandController* controller) : ToggleButton(), + location_bar_(location_bar), + controller_(controller), + intended_mode_(MODE_GO), + visible_mode_(MODE_GO), + button_delay_(NULL), + stop_timer_(this) { + DCHECK(location_bar_); +} + +GoButton::~GoButton() { + stop_timer_.RevokeAll(); +} + +void GoButton::NotifyClick(int mouse_event_flags) { + if (visible_mode_ == MODE_STOP) { + controller_->ExecuteCommand(IDC_STOP); + + // The user has clicked, so we can feel free to update the button, + // even if the mouse is still hovering. + ChangeMode(MODE_GO); + } else if (visible_mode_ == MODE_GO && stop_timer_.empty()) { + // If the go button is visible and not within the doubleclick timer, go. + controller_->ExecuteCommand(IDC_GO); + + // Figure out the system double-click time. + if (button_delay_ == NULL) + button_delay_ = GetDoubleClickTime(); + + // Stop any existing timers. + stop_timer_.RevokeAll(); + + // Start a timer - while this timer is running, the go button + // cannot be changed to a stop button. We do not set intended_mode_ + // to MODE_STOP here as we want to wait for the browser to tell + // us that it has started loading (and this may occur only after + // some delay). + MessageLoop::current()->PostDelayedTask(FROM_HERE, + stop_timer_.NewRunnableMethod(&GoButton::OnButtonTimer), + button_delay_); + } +} + +void GoButton::OnMouseExited(const ChromeViews::MouseEvent& e) { + using namespace ChromeViews; + + if (visible_mode_ != intended_mode_) + ChangeMode(intended_mode_); + + if (GetState() != BS_DISABLED) + SetState(BS_NORMAL); +} + +void GoButton::ChangeMode(Mode mode) { + stop_timer_.RevokeAll(); + + SetToggled(mode == MODE_STOP); + intended_mode_ = mode; + visible_mode_ = mode; +} + +void GoButton::ScheduleChangeMode(Mode mode) { + if (mode == MODE_STOP) { + // If we still have a timer running, we can't yet change to a stop sign, + // so we'll queue up the change for when the timer expires or for when + // the mouse exits the button. + if (!stop_timer_.empty() && GetState() == BS_HOT) { + intended_mode_ = MODE_STOP; + } else { + ChangeMode(MODE_STOP); + } + } else { + // If we want to change the button to a go button, but the user's mouse + // is hovering, don't change the mode just yet - this prevents the + // stop button changing to a go under the user's mouse cursor. + if (visible_mode_ == MODE_STOP && GetState() == BS_HOT) { + intended_mode_ = MODE_GO; + } else { + ChangeMode(MODE_GO); + } + } +} + +void GoButton::OnButtonTimer() { + if (intended_mode_ != visible_mode_) + ChangeMode(intended_mode_); + + stop_timer_.RevokeAll(); +} + +bool GoButton::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (visible_mode_ == MODE_STOP) { + tooltip->assign(l10n_util::GetString(IDS_TOOLTIP_STOP)); + return true; + } + + std::wstring current_text(location_bar_->location_entry()->GetText()); + if (current_text.empty()) + return false; + + // Need to make sure the text direction is adjusted based on the locale so + // that pure LTR strings are displayed appropriately on RTL locales. For + // example, not adjusting the string will cause the URL + // "http://www.google.com/" to be displayed in the tooltip as + // "/http://www.google.com". + // + // Note that we mark the URL's text as LTR (instead of examining the + // characters and guessing the text directionality) since URLs are always + // treated as left-to-right text, even when they contain RTL characters. + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + l10n_util::WrapStringWithLTRFormatting(¤t_text); + + // TODO(pkasting): http://b/868940 Use the right strings at the right times by + // asking the autocomplete system what to do. Don't hardcode "Google" as the + // search provider name. + tooltip->assign(true ? + l10n_util::GetStringF(IDS_TOOLTIP_GO_SITE, current_text) : + l10n_util::GetStringF(IDS_TOOLTIP_GO_SEARCH, L"Google", current_text)); + return true; +} diff --git a/chrome/browser/views/go_button.h b/chrome/browser/views/go_button.h new file mode 100644 index 0000000..72060f9 --- /dev/null +++ b/chrome/browser/views/go_button.h @@ -0,0 +1,89 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_GO_BUTTON_H__ +#define CHROME_BROWSER_VIEWS_GO_BUTTON_H__ + +#include "chrome/views/button.h" +#include "chrome/browser/controller.h" +#include "base/task.h" + +class LocationBarView; + +//////////////////////////////////////////////////////////////////////////////// +// +// GoButton +// +// The go button attached to the toolbar. It shows different tooltips +// according to the content of the location bar and changes to a stop +// button when a page load is in progress. Trickiness comes from the +// desire to have the 'stop' button not change back to 'go' if the user's +// mouse is hovering over it (to prevent misclicks). +// +//////////////////////////////////////////////////////////////////////////////// + +class GoButton : public ChromeViews::ToggleButton { + public: + GoButton(LocationBarView* location_bar, CommandController* controller); + virtual ~GoButton(); + + typedef enum Mode { MODE_GO = 0, MODE_STOP }; + + virtual void NotifyClick(int mouse_event_flags); + virtual void OnMouseExited(const ChromeViews::MouseEvent& e); + + // Force the button state + void ChangeMode(Mode mode); + + // Ask for a specified button state. This is commonly called by the Browser + // when page load state changes. + void ScheduleChangeMode(Mode mode); + + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + private: + void OnButtonTimer(); + + int button_delay_; + ScopedRunnableMethodFactory<GoButton> stop_timer_; + + LocationBarView* location_bar_; + CommandController* controller_; + ButtonListener* listener_; + + // The mode we should be in + Mode intended_mode_; + + // The currently-visible mode - this may different from the intended mode + Mode visible_mode_; + + DISALLOW_EVIL_CONSTRUCTORS(GoButton); +}; + +#endif // CHROME_BROWSER_VIEWS_GO_BUTTON_H__ diff --git a/chrome/browser/views/html_dialog_view.cc b/chrome/browser/views/html_dialog_view.cc new file mode 100644 index 0000000..1be835f --- /dev/null +++ b/chrome/browser/views/html_dialog_view.cc @@ -0,0 +1,201 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/browser.h" +#include "chrome/browser/views/html_dialog_view.h" + +#include "chrome/views/window.h" + +//////////////////////////////////////////////////////////////////////////////// +// HtmlDialogView, public: + +HtmlDialogView::HtmlDialogView(Browser* parent_browser, + Profile* profile, + HtmlDialogContentsDelegate* delegate) + : DOMView(delegate->GetDialogContentURL()), + dialog_(NULL), + parent_browser_(parent_browser), + profile_(profile), + delegate_(delegate) { + DCHECK(parent_browser); + DCHECK(profile); +} + +HtmlDialogView::~HtmlDialogView() { +} + +//////////////////////////////////////////////////////////////////////////////// +// HtmlDialogView, ChromeViews::View implementation: + +void HtmlDialogView::GetPreferredSize(CSize *out) { + delegate_->GetDialogSize(out); +} + +//////////////////////////////////////////////////////////////////////////////// +// HtmlDialogView, ChromeViews::WindowDelegate implementation: + +bool HtmlDialogView::CanResize() const { + return true; +} + +bool HtmlDialogView::IsModal() const { + return delegate_->IsModal(); +} + +std::wstring HtmlDialogView::GetWindowTitle() const { + return L"Google Gears"; +} + +void HtmlDialogView::WindowClosing() { + // If we still have a delegate that means we haven't notified it of the + // dialog closing. This happens if the user clicks the Close button on the + // dialog. + if (delegate_) + OnDialogClosed(""); +} + +//////////////////////////////////////////////////////////////////////////////// +// HtmlDialogContentsDelegate implementation: + +GURL HtmlDialogView::GetDialogContentURL() const { + return delegate_->GetDialogContentURL(); +} + +void HtmlDialogView::GetDialogSize(CSize* size) const { + return delegate_->GetDialogSize(size); +} + +std::string HtmlDialogView::GetDialogArgs() const { + return delegate_->GetDialogArgs(); +} + +void HtmlDialogView::OnDialogClosed(const std::string& json_retval) { + delegate_->OnDialogClosed(json_retval); + delegate_ = NULL; // We will not communicate further with the delegate. + dialog_->Close(); +} + +//////////////////////////////////////////////////////////////////////////////// +// PageNavigator implementation: +void HtmlDialogView::OpenURLFromTab(TabContents* source, + const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + // Force all links to open in a new window, ignoring the incoming + // disposition. This is a tabless, modal dialog so we can't just + // open it in the current frame. + parent_browser_->OpenURLFromTab(source, url, NEW_WINDOW, transition); +} + +//////////////////////////////////////////////////////////////////////////////// +// TabContentsDelegate implementation: + +void HtmlDialogView::NavigationStateChanged(const TabContents* source, + unsigned changed_flags) { + // We shouldn't receive any NavigationStateChanged except the first + // one, which we ignore because we're a dialog box. +} + +void HtmlDialogView::ReplaceContents(TabContents* source, + TabContents* new_contents) { +} + +void HtmlDialogView::AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + parent_browser_->AddNewContents( + source, new_contents, NEW_WINDOW, initial_pos, user_gesture); +} + +void HtmlDialogView::ActivateContents(TabContents* contents) { + // We don't do anything here because there's only one TabContents in + // this frame and we don't have a TabStripModel. +} + +void HtmlDialogView::LoadingStateChanged(TabContents* source) { + // We don't care about this notification +} + +void HtmlDialogView::CloseContents(TabContents* source) { + // We receive this message but don't handle it because we really do the + // cleanup in OnDialogClosed(). +} + +void HtmlDialogView::MoveContents(TabContents* source, const gfx::Rect& pos) { + // The contained web page wishes to resize itself. We let it do this because + // if it's a dialog we know about, we trust it not to be mean to the user. + + // Determine the size the window containing the dialog at its requested size. + gfx::Size window_size = + dialog_->CalculateWindowSizeForClientSize(pos.size()); + + // Actually size the window. + CRect vc_bounds; + GetViewContainer()->GetBounds(&vc_bounds, true); + gfx::Rect bounds(vc_bounds.left, vc_bounds.top, window_size.width(), + window_size.height()); + dialog_->SetBounds(bounds); +} + +bool HtmlDialogView::IsPopup(TabContents* source) { + // This needs to return true so that we are allowed to be resized by our + // contents. + return true; +} + +void HtmlDialogView::ToolbarSizeChanged(TabContents* source, bool is_animating) { + Layout(); +} + +void HtmlDialogView::URLStarredChanged(TabContents* source, bool starred) { + // We don't have a visible star to click in the window. + NOTREACHED(); +} + +void HtmlDialogView::UpdateTargetURL(TabContents* source, const GURL& url) { + // Ignored. +} + +//////////////////////////////////////////////////////////////////////////////// +// HtmlDialogView: + +void HtmlDialogView::InitDialog(ChromeViews::Window* dialog) { + dialog_ = dialog; + + // Now Init the DOMView. This view runs in its own process to render the html. + DOMView::Init(profile_, NULL); + + // Make sure this new TabContents we just created in Init() knows about us. + DCHECK(host_->type() == TAB_CONTENTS_HTML_DIALOG); + HtmlDialogContents* host = static_cast<HtmlDialogContents*>(host_); + host->Init(this); + host->set_delegate(this); +} diff --git a/chrome/browser/views/html_dialog_view.h b/chrome/browser/views/html_dialog_view.h new file mode 100644 index 0000000..54f8f15 --- /dev/null +++ b/chrome/browser/views/html_dialog_view.h @@ -0,0 +1,122 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_HTML_DIALOG_VIEW_H__ +#define CHROME_BROWSER_VIEWS_HTML_DIALOG_VIEW_H__ + +#include <string> + +#include "chrome/browser/dom_ui/html_dialog_contents.h" +#include "chrome/browser/tab_contents_delegate.h" +#include "chrome/browser/views/dom_view.h" + +namespace ChromeViews { + class Window; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// HtmlDialogView is a view used to display an HTML dialog to the user. The +// content of the dialogs is determined by the delegate +// (HtmlDialogContentsDelegate), but is basically a file URL along with a +// JSON input string. The HTML is supposed to show a UI to the user and is +// expected to send back a JSON file as a return value. +// +//////////////////////////////////////////////////////////////////////////////// +class HtmlDialogView + : public DOMView, + public HtmlDialogContentsDelegate, + public TabContentsDelegate { + public: + HtmlDialogView(Browser* parent_browser, + Profile* profile, + HtmlDialogContentsDelegate* delegate); + virtual ~HtmlDialogView(); + + // Initializes the contents of the dialog (the DOMView and the callbacks). + void InitDialog(ChromeViews::Window* dialog); + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + + // Overridden from ChromeViews::WindowDelegate: + virtual bool CanResize() const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual void WindowClosing(); + + // Overridden from HtmlDialogContentsDelegate: + virtual GURL GetDialogContentURL() const; + virtual void GetDialogSize(CSize* size) const; + virtual std::string GetDialogArgs() const; + virtual void OnDialogClosed(const std::string& json_retval); + + // Overridden from TabContentsDelegate: + virtual void OpenURLFromTab(TabContents* source, + const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition); + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags); + virtual void ReplaceContents(TabContents* source, + TabContents* new_contents); + virtual void AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ActivateContents(TabContents* contents); + virtual void LoadingStateChanged(TabContents* source); + virtual void CloseContents(TabContents* source); + virtual void MoveContents(TabContents* source, const gfx::Rect& pos); + virtual bool IsPopup(TabContents* source); + virtual void ToolbarSizeChanged(TabContents* source, bool is_animating); + virtual void URLStarredChanged(TabContents* source, bool starred); + virtual void UpdateTargetURL(TabContents* source, const GURL& url); + + private: + // The dialog this view is displayed in. + ChromeViews::Window* dialog_; + + // The Browser object which created this html dialog; we send all + // window opening/navigations to this object. + Browser* parent_browser_; + + Profile* profile_; + + // This view is a delegate to the HTML content since it needs to get notified + // about when the dialog is closing. For all other actions (besides dialog + // closing) we delegate to the creator of this view, which we keep track of + // using this variable. + HtmlDialogContentsDelegate* delegate_; + + DISALLOW_EVIL_CONSTRUCTORS(HtmlDialogView); +}; + +#endif // CHROME_BROWSER_VIEWS_HTML_DIALOG_VIEW_H__ diff --git a/chrome/browser/views/hung_renderer_view.cc b/chrome/browser/views/hung_renderer_view.cc new file mode 100644 index 0000000..efb03c5 --- /dev/null +++ b/chrome/browser/views/hung_renderer_view.cc @@ -0,0 +1,475 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/hung_renderer_view.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/client_view.h" +#include "chrome/views/custom_frame_window.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/group_table_view.h" +#include "chrome/views/image_view.h" +#include "chrome/views/native_button.h" +#include "generated_resources.h" + +/////////////////////////////////////////////////////////////////////////////// +// HungPagesTableModel + +class HungPagesTableModel : public ChromeViews::GroupTableModel { + public: + HungPagesTableModel(); + virtual ~HungPagesTableModel(); + + void InitForWebContents(WebContents* hung_contents); + + // Overridden from ChromeViews::GroupTableModel: + virtual int RowCount(); + virtual std::wstring GetText(int row, int column_id); + virtual SkBitmap GetIcon(int row); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + virtual void GetGroupRangeForItem(int item, ChromeViews::GroupRange* range); + + private: + typedef std::vector<WebContents*> WebContentsVector; + WebContentsVector webcontentses_; + + ChromeViews::TableModelObserver* observer_; + + DISALLOW_EVIL_CONSTRUCTORS(HungPagesTableModel); +}; + +/////////////////////////////////////////////////////////////////////////////// +// HungPagesTableModel, public: + +HungPagesTableModel::HungPagesTableModel() : observer_(NULL) { +} + +HungPagesTableModel::~HungPagesTableModel() { +} + +void HungPagesTableModel::InitForWebContents(WebContents* hung_contents) { + webcontentses_.clear(); + for (WebContentsIterator it; !it.done(); ++it) { + if (it->process() == hung_contents->process()) + webcontentses_.push_back(*it); + } + // The world is different. + if (observer_) + observer_->OnModelChanged(); +} + +/////////////////////////////////////////////////////////////////////////////// +// HungPagesTableModel, ChromeViews::GroupTableModel implementation: + +int HungPagesTableModel::RowCount() { + return static_cast<int>(webcontentses_.size()); +} + +std::wstring HungPagesTableModel::GetText(int row, int column_id) { + DCHECK(row >= 0 && row < RowCount()); + std::wstring title = webcontentses_.at(row)->GetTitle(); + if (title.empty()) + title = l10n_util::GetString(IDS_TAB_UNTITLED_TITLE); + return title; +} + +SkBitmap HungPagesTableModel::GetIcon(int row) { + DCHECK(row >= 0 && row < RowCount()); + return webcontentses_.at(row)->GetFavIcon(); +} + +void HungPagesTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +void HungPagesTableModel::GetGroupRangeForItem( + int item, + ChromeViews::GroupRange* range) { + DCHECK(range); + range->start = 0; + range->length = RowCount(); +} + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarningView + +class HungRendererWarningView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::NativeButton::Listener { + public: + HungRendererWarningView(); + ~HungRendererWarningView(); + + void ShowForWebContents(WebContents* contents); + void EndForWebContents(WebContents* contents); + + void set_window(ChromeViews::Window* window) { window_ = window; } + + // ChromeViews::WindowDelegate overrides: + virtual std::wstring GetWindowTitle() const; + virtual void WindowClosing(); + virtual int GetDialogButtons() const; + virtual std::wstring GetDialogButtonLabel( + ChromeViews::DialogDelegate::DialogButton button) const; + virtual ChromeViews::View* GetExtraView(); + virtual bool Accept(bool window_closing); + + // ChromeViews::NativeButton::Listener overrides: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Initialize the controls in this dialog. + void Init(); + void CreateKillButtonView(); + + // Returns the bounds the dialog should be displayed at to be meaningfully + // associated with the specified WebContents. + gfx::Rect GetDisplayBounds(WebContents* contents); + + static void InitClass(); + + // Controls within the dialog box. + ChromeViews::ImageView* frozen_icon_view_; + ChromeViews::Label* info_label_; + ChromeViews::GroupTableView* hung_pages_table_; + + // The button we insert into the ClientView to kill the errant process. This + // is parented to a container view that uses a grid layout to align it + // properly. + ChromeViews::NativeButton* kill_button_; + class ButtonContainer : public ChromeViews::View { + public: + ButtonContainer() {} + virtual ~ButtonContainer() {} + + virtual void DidChangeBounds(const CRect& previous, const CRect& current) { + Layout(); + } + private: + DISALLOW_EVIL_CONSTRUCTORS(ButtonContainer); + }; + ButtonContainer* kill_button_container_; + + // The Window that contains this view. + ChromeViews::Window* window_; + + // The model that provides the contents of the table that shows a list of + // pages affected by the hang. + scoped_ptr<HungPagesTableModel> hung_pages_table_model_; + + // The WebContents that we detected had hung in the first place resulting in + // the display of this view. + WebContents* contents_; + + // Whether or not we've created controls for ourself. + bool initialized_; + + // An amusing icon image. + static SkBitmap* frozen_icon_; + + DISALLOW_EVIL_CONSTRUCTORS(HungRendererWarningView); +}; + +// static +SkBitmap* HungRendererWarningView::frozen_icon_ = NULL; + +// The distance in pixels from the top of the relevant contents to place the +// warning window. +static const int kOverlayContentsOffsetY = 50; + +// The dimensions of the hung pages list table view, in pixels. +static const int kTableViewWidth = 300; +static const int kTableViewHeight = 100; + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarningView, public: + +HungRendererWarningView::HungRendererWarningView() + : frozen_icon_view_(NULL), + info_label_(NULL), + hung_pages_table_(NULL), + kill_button_(NULL), + kill_button_container_(NULL), + window_(NULL), + contents_(NULL), + initialized_(false) { + InitClass(); +} + +HungRendererWarningView::~HungRendererWarningView() { + hung_pages_table_->SetModel(NULL); +} + +void HungRendererWarningView::ShowForWebContents(WebContents* contents) { + DCHECK(contents && window_); + contents_ = contents; + + // Don't show the warning unless the foreground window is the frame, or this + // window (but still invisible). If the user has another window or + // application selected, activating ourselves is rude. + HWND frame_hwnd = GetAncestor(contents->GetContainerHWND(), GA_ROOT); + HWND foreground_window = GetForegroundWindow(); + if (foreground_window != frame_hwnd && + foreground_window != window_->GetHWND()) { + return; + } + + if (!window_->IsActive()) { + gfx::Rect bounds = GetDisplayBounds(contents); + window_->SetBounds(bounds, frame_hwnd); + + // We only do this if the window isn't active (i.e. hasn't been shown yet, + // or is currently shown but deactivated for another WebContents). This is + // because this window is a singleton, and it's possible another active + // renderer may hang while this one is showing, and we don't want to reset + // the list of hung pages for a potentially unrelated renderer while this + // one is showing. + hung_pages_table_model_->InitForWebContents(contents); + window_->Show(); + } +} + +void HungRendererWarningView::EndForWebContents(WebContents* contents) { + DCHECK(contents); + if (contents_ && contents_->process() == contents->process()) { + window_->Close(); + // Since we're closing, we no longer need this WebContents. + contents_ = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarningView, ChromeViews::DialogDelegate implementation: + +std::wstring HungRendererWarningView::GetWindowTitle() const { + return l10n_util::GetString(IDS_PRODUCT_NAME); +} + +void HungRendererWarningView::WindowClosing() { + // We are going to be deleted soon, so make sure our instance is destroyed. + HungRendererWarning::instance_ = NULL; +} + +int HungRendererWarningView::GetDialogButtons() const { + // We specifically don't want a CANCEL button here because that code path is + // also called when the window is closed by the user clicking the X button in + // the window's titlebar, and also if we call Window::Close. Rather, we want + // the OK button to wait for responsiveness (and close the dialog) and our + // additional button (which we create) to kill the process (which will result + // in the dialog being destroyed). + return DIALOGBUTTON_OK; +} + +std::wstring HungRendererWarningView::GetDialogButtonLabel( + ChromeViews::DialogDelegate::DialogButton button) const { + if (button == DIALOGBUTTON_OK) + return l10n_util::GetString(IDS_BROWSER_HANGMONITOR_RENDERER_WAIT); + return std::wstring(); +} + +ChromeViews::View* HungRendererWarningView::GetExtraView() { + return kill_button_container_; +} + +bool HungRendererWarningView::Accept(bool window_closing) { + // Don't do anything if we're being called only because the dialog is being + // destroyed and we don't supply a Cancel function... + if (window_closing) + return true; + + // Start waiting again for responsiveness. + if (contents_ && contents_->render_view_host()) + contents_->render_view_host()->ResetHangMonitorTimeout(); + return true; +} + +void HungRendererWarningView::ButtonPressed( + ChromeViews::NativeButton* sender) { + if (sender == kill_button_) { + // Kill the process. + HANDLE process = contents_->process()->process(); + TerminateProcess(process, 0); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarningView, ChromeViews::View overrides: + +void HungRendererWarningView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (!initialized_ && is_add && child == this && GetViewContainer()) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarningView, private: + +void HungRendererWarningView::Init() { + frozen_icon_view_ = new ChromeViews::ImageView; + frozen_icon_view_->SetImage(frozen_icon_); + + info_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_BROWSER_HANGMONITOR_RENDERER)); + info_label_->SetMultiLine(true); + info_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + + hung_pages_table_model_.reset(new HungPagesTableModel); + std::vector<ChromeViews::TableColumn> columns; + columns.push_back(ChromeViews::TableColumn()); + hung_pages_table_ = new ChromeViews::GroupTableView( + hung_pages_table_model_.get(), columns, ChromeViews::ICON_AND_TEXT, true, + false, true); + hung_pages_table_->SetPreferredSize( + CSize(kTableViewWidth, kTableViewHeight)); + + CreateKillButtonView(); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int double_column_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(double_column_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::FIXED, frozen_icon_->width(), 0); + column_set->AddPaddingColumn(0, kUnrelatedControlLargeHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, double_column_set_id); + layout->AddView(frozen_icon_view_, 1, 3); + layout->AddView(info_label_); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + layout->StartRow(0, double_column_set_id); + layout->SkipColumns(1); + layout->AddView(hung_pages_table_); + + initialized_ = true; +} + +void HungRendererWarningView::CreateKillButtonView() { + kill_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_BROWSER_HANGMONITOR_RENDERER_END)); + kill_button_->SetListener(this); + + kill_button_container_ = new ButtonContainer; + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(kill_button_container_); + kill_button_container_->SetLayoutManager(layout); + + const int single_column_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_set_id); + column_set->AddPaddingColumn(0, frozen_icon_->width() + + kPanelHorizMargin + kUnrelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_set_id); + layout->AddView(kill_button_); +} + +gfx::Rect HungRendererWarningView::GetDisplayBounds( + WebContents* contents) { + HWND contents_hwnd = contents->GetContainerHWND(); + CRect contents_bounds; + GetWindowRect(contents_hwnd, &contents_bounds); + + CRect window_bounds; + window_->GetBounds(&window_bounds, true); + + int window_x = contents_bounds.left + + (contents_bounds.Width() - window_bounds.Width()) / 2; + int window_y = contents_bounds.top + kOverlayContentsOffsetY; + return gfx::Rect(window_x, window_y, window_bounds.Width(), + window_bounds.Height()); +} + +// static +void HungRendererWarningView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + frozen_icon_ = rb.GetBitmapNamed(IDR_FROZEN_TAB_ICON); + initialized = true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// HungRendererWarning + +// static +HungRendererWarningView* HungRendererWarning::instance_ = NULL; + +static HungRendererWarningView* CreateHungRendererWarningView() { + HungRendererWarningView* cv = new HungRendererWarningView; + ChromeViews::Window* window = + ChromeViews::Window::CreateChromeWindow(NULL, gfx::Rect(), cv, cv); + cv->set_window(window); + return cv; +} + +// static +void HungRendererWarning::ShowForWebContents(WebContents* contents) { + if (!logging::DialogsAreSuppressed()) { + if (!instance_) + instance_ = CreateHungRendererWarningView(); + instance_->ShowForWebContents(contents); + } +} + +// static +void HungRendererWarning::HideForWebContents(WebContents* contents) { + if (!logging::DialogsAreSuppressed() && instance_) + instance_->EndForWebContents(contents); +} diff --git a/chrome/browser/views/hung_renderer_view.h b/chrome/browser/views/hung_renderer_view.h new file mode 100644 index 0000000..19e410e --- /dev/null +++ b/chrome/browser/views/hung_renderer_view.h @@ -0,0 +1,56 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_HUNG_RENDERER_VIEW_H__ +#define CHROME_BROWSER_VIEWS_HUNG_RENDERER_VIEW_H__ + +#include "base/logging.h" + +class HungRendererWarningView; +namespace ChromeViews { +class Window; +} +class WebContents; + +class HungRendererWarning { + public: + static void ShowForWebContents(WebContents* contents); + static void HideForWebContents(WebContents* contents); + + private: + friend HungRendererWarningView; + + // We only support showing one of these at a time per app. + static HungRendererWarningView* instance_; + + DISALLOW_EVIL_CONSTRUCTORS(HungRendererWarning); +}; + + +#endif // CHROME_BROWSER_VIEWS_HUNG_RENDERER_VIEW_H__ diff --git a/chrome/browser/views/importer_lock_view.cc b/chrome/browser/views/importer_lock_view.cc new file mode 100644 index 0000000..9e4b00e --- /dev/null +++ b/chrome/browser/views/importer_lock_view.cc @@ -0,0 +1,99 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/importer_lock_view.h" + +#include "chrome/browser/importer.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/label.h" + +#include "generated_resources.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; + +// Default size of the dialog window. +static const int kDefaultWindowWidth = 320; +static const int kDefaultWindowHeight = 100; + +ImporterLockView::ImporterLockView(ImporterHost* host) + : dialog_(NULL), + description_label_(NULL), + importer_host_(host) { + description_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORTER_LOCK_TEXT)); + description_label_->SetMultiLine(true); + description_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + AddChildView(description_label_); +} + +ImporterLockView::~ImporterLockView() { +} + +void ImporterLockView::GetPreferredSize(CSize *out) { + out->cx = kDefaultWindowWidth; + out->cy = kDefaultWindowHeight; +} + +void ImporterLockView::Layout() { + description_label_->SetBounds(kPanelHorizMargin, kPanelVertMargin, + kDefaultWindowWidth - 2 * kPanelHorizMargin, + kDefaultWindowHeight - 2 * kPanelVertMargin); +} + +std::wstring ImporterLockView::GetDialogButtonLabel( + DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + return l10n_util::GetString(IDS_IMPORTER_LOCK_OK); + } else if (button == DIALOGBUTTON_CANCEL) { + return l10n_util::GetString(IDS_IMPORTER_LOCK_CANCEL); + } + return std::wstring(); +} + +bool ImporterLockView::IsModal() const { + return true; +} + +std::wstring ImporterLockView::GetWindowTitle() const { + return l10n_util::GetString(IDS_IMPORTER_LOCK_TITLE); +} + +bool ImporterLockView::Accept() { + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + importer_host_, &ImporterHost::OnLockViewEnd, true)); + return true; +} + +bool ImporterLockView::Cancel() { + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + importer_host_, &ImporterHost::OnLockViewEnd, false)); + return true; +} diff --git a/chrome/browser/views/importer_lock_view.h b/chrome/browser/views/importer_lock_view.h new file mode 100644 index 0000000..876560a --- /dev/null +++ b/chrome/browser/views/importer_lock_view.h @@ -0,0 +1,78 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_IMPORTER_LOCK_VIEW_H__ +#define CHROME_BROWSER_VIEWS_IMPORTER_LOCK_VIEW_H__ + +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class Label; +class Window; + +} + +class ImporterHost; + +// ImporterLockView draws the dialog, and asks the user to shut Firefox +// down before starting the import. +class ImporterLockView : public ChromeViews::View, + public ChromeViews::DialogDelegate { + public: + explicit ImporterLockView(ImporterHost* host); + virtual ~ImporterLockView(); + + void set_dialog(ChromeViews::Window* dialog) { + dialog_ = dialog; + } + + // Overridden from ChromeViews::View. + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + // Overridden from ChromeViews::DialogDelegate: + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + virtual bool Cancel(); + + private: + ChromeViews::Label* description_label_; + + ChromeViews::Window* dialog_; + + ImporterHost* importer_host_; + + DISALLOW_EVIL_CONSTRUCTORS(ImporterLockView); +}; + +#endif // CHROME_BROWSER_VIEWS_IMPORTER_LOCK_VIEW_H__ diff --git a/chrome/browser/views/importer_view.cc b/chrome/browser/views/importer_view.cc new file mode 100644 index 0000000..bd71637 --- /dev/null +++ b/chrome/browser/views/importer_view.cc @@ -0,0 +1,199 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/importer_view.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; + +ImporterView::ImporterView(Profile* profile) + : dialog_(NULL), + import_from_label_(NULL), + profile_combobox_(NULL), + import_items_label_(NULL), + history_checkbox_(NULL), + favorites_checkbox_(NULL), + passwords_checkbox_(NULL), + search_engines_checkbox_(NULL), + profile_(profile), + importer_host_(new ImporterHost()) { + DCHECK(profile); + SetupControl(); +} + +ImporterView::~ImporterView() { +} + +void ImporterView::SetupControl() { + // Adds all controls. + import_from_label_ = + new ChromeViews::Label(l10n_util::GetString(IDS_IMPORT_FROM_LABEL)); + + profile_combobox_ = new ChromeViews::ComboBox(this); + + import_items_label_ = + new ChromeViews::Label(l10n_util::GetString(IDS_IMPORT_ITEMS_LABEL)); + + history_checkbox_ = + InitCheckbox(l10n_util::GetString(IDS_IMPORT_HISTORY_CHKBOX), true); + favorites_checkbox_ = + InitCheckbox(l10n_util::GetString(IDS_IMPORT_FAVORITES_CHKBOX), true); + passwords_checkbox_ = + InitCheckbox(l10n_util::GetString(IDS_IMPORT_PASSWORDS_CHKBOX), true); + search_engines_checkbox_ = + InitCheckbox(l10n_util::GetString(IDS_IMPORT_SEARCH_ENGINES_CHKBOX), + true); + + // Arranges controls by using GridLayout. + const int column_set_id = 0; + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + ColumnSet* column_set = layout->AddColumnSet(column_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::FIXED, 200, 0); + + layout->StartRow(0, column_set_id); + layout->AddView(import_from_label_); + layout->AddView(profile_combobox_); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->AddView(import_items_label_, 3, 1); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->AddView(favorites_checkbox_, 3, 1); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->AddView(search_engines_checkbox_, 3, 1); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->AddView(passwords_checkbox_, 3, 1); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, column_set_id); + layout->AddView(history_checkbox_, 3, 1); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); +} + +void ImporterView::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_IMPORT_DIALOG_WIDTH_CHARS, + IDS_IMPORT_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void ImporterView::Layout() { + GetLayoutManager()->Layout(this); +} + +std::wstring ImporterView::GetDialogButtonLabel( + DialogButton button) const { + if (button == DIALOGBUTTON_OK) { + return l10n_util::GetString(IDS_IMPORT_COMMIT); + } else { + return std::wstring(); + } +} + +bool ImporterView::IsModal() const { + return true; +} + +std::wstring ImporterView::GetWindowTitle() const { + return l10n_util::GetString(IDS_IMPORT_SETTINGS_TITLE); +} + +bool ImporterView::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) { + return false; + } + + uint16 items = NONE; + if (history_checkbox_->IsEnabled() && history_checkbox_->IsSelected()) + items |= HISTORY; + if (favorites_checkbox_->IsEnabled() && favorites_checkbox_->IsSelected()) + items |= FAVORITES; + if (passwords_checkbox_->IsEnabled() && passwords_checkbox_->IsSelected()) + items |= PASSWORDS; + if (search_engines_checkbox_->IsEnabled() && + search_engines_checkbox_->IsSelected()) + items |= SEARCH_ENGINES; + + Browser* browser = BrowserList::GetLastActive(); + int selected_index = profile_combobox_->GetSelectedItem(); + StartImportingWithUI(browser->GetTopLevelHWND(), items, importer_host_.get(), + importer_host_->GetSourceProfileInfoAt(selected_index), + profile_, this, false); + // We return false here to prevent the window from being closed. We will be + // notified back by our implementation of ImportObserver when the import is + // complete so that we can close ourselves. + return false; +} + +int ImporterView::GetItemCount(ChromeViews::ComboBox* source) { + DCHECK(source == profile_combobox_); + DCHECK(importer_host_.get()); + return importer_host_->GetAvailableProfileCount(); +} + +std::wstring ImporterView::GetItemAt(ChromeViews::ComboBox* source, + int index) { + DCHECK(source == profile_combobox_); + DCHECK(importer_host_.get()); + return importer_host_->GetSourceProfileNameAt(index); +} + +void ImporterView::ImportCanceled() { + ImportComplete(); +} + +void ImporterView::ImportComplete() { + // Now close this window since the import completed or was canceled. + dialog_->Close(); +} + +ChromeViews::CheckBox* ImporterView::InitCheckbox( + const std::wstring& text, bool checked) { + ChromeViews::CheckBox* checkbox = new ChromeViews::CheckBox(text); + checkbox->SetIsSelected(checked); + return checkbox; +} diff --git a/chrome/browser/views/importer_view.h b/chrome/browser/views/importer_view.h new file mode 100644 index 0000000..fe9ab11 --- /dev/null +++ b/chrome/browser/views/importer_view.h @@ -0,0 +1,105 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_IMPORTER_VIEW_H__ +#define CHROME_BROWSER_VIEWS_IMPORTER_VIEW_H__ + +#include "chrome/browser/importer.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { + +class CheckBox; +class Label; +class Window; + +} + +class Profile; + +// ImporterView draws the dialog that allows the user to select what to +// import from other browsers. +// Note: The UI team hasn't defined yet how the import UI will look like. +// So now use dialog as a placeholder. +class ImporterView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::ComboBox::Model, + public ImportObserver { + public: + explicit ImporterView(Profile* profile); + virtual ~ImporterView(); + + void set_dialog(ChromeViews::Window* dialog) { dialog_ = dialog; } + + // Overridden from ChromeViews::View. + virtual void GetPreferredSize(CSize *out); + virtual void Layout(); + + // Overridden from ChromeViews::DialogDelegate: + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Accept(); + + // Overridden from ChromeViews::ComboBox::Model. + virtual int GetItemCount(ChromeViews::ComboBox* source); + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Overridden from ImportObserver: + virtual void ImportCanceled(); + virtual void ImportComplete(); + + private: + // Initializes the controls on the dialog. + void SetupControl(); + + // Creates and initializes a new check-box. + ChromeViews::CheckBox* InitCheckbox(const std::wstring& text, bool checked); + + ChromeViews::Label* import_from_label_; + ChromeViews::ComboBox* profile_combobox_; + ChromeViews::Label* import_items_label_; + ChromeViews::CheckBox* history_checkbox_; + ChromeViews::CheckBox* favorites_checkbox_; + ChromeViews::CheckBox* passwords_checkbox_; + ChromeViews::CheckBox* search_engines_checkbox_; + + ChromeViews::Window* dialog_; + + scoped_refptr<ImporterHost> importer_host_; + + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(ImporterView); +}; + +#endif // CHROME_BROWSER_VIEWS_IMPORTER_VIEW_H__ diff --git a/chrome/browser/views/importing_progress_view.cc b/chrome/browser/views/importing_progress_view.cc new file mode 100644 index 0000000..aa99455 --- /dev/null +++ b/chrome/browser/views/importing_progress_view.cc @@ -0,0 +1,306 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/importing_progress_view.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/throbber.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +//////////////////////////////////////////////////////////////////////////////// +// ImportingProgressView, public: + +ImportingProgressView::ImportingProgressView(const std::wstring& source_name, + int16 items, + ImporterHost* coordinator, + ImportObserver* observer, + HWND parent_window) + : state_bookmarks_(new ChromeViews::CheckmarkThrobber), + state_searches_(new ChromeViews::CheckmarkThrobber), + state_passwords_(new ChromeViews::CheckmarkThrobber), + state_history_(new ChromeViews::CheckmarkThrobber), + state_cookies_(new ChromeViews::CheckmarkThrobber), + label_info_(new ChromeViews::Label(l10n_util::GetStringF( + IDS_IMPORT_PROGRESS_INFO, source_name))), + label_bookmarks_(new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_BOOKMARKS))), + label_searches_(new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_SEARCH))), + label_passwords_(new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_PASSWORDS))), + label_history_(new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_HISTORY))), + label_cookies_(new ChromeViews::Label( + l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_COOKIES))), + window_(NULL), + parent_window_(parent_window), + coordinator_(coordinator), + import_observer_(observer), + items_(items), + importing_(false) { + coordinator_->SetObserver(this); + label_info_->SetMultiLine(true); + label_info_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label_bookmarks_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label_searches_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label_passwords_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label_history_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label_cookies_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + + // These are scoped pointers, so we don't need the parent to delete them. + state_bookmarks_->SetParentOwned(false); + state_searches_->SetParentOwned(false); + state_passwords_->SetParentOwned(false); + state_history_->SetParentOwned(false); + state_cookies_->SetParentOwned(false); + label_bookmarks_->SetParentOwned(false); + label_searches_->SetParentOwned(false); + label_passwords_->SetParentOwned(false); + label_history_->SetParentOwned(false); + label_cookies_->SetParentOwned(false); +} + +ImportingProgressView::~ImportingProgressView() { + RemoveChildView(state_bookmarks_.get()); + RemoveChildView(state_searches_.get()); + RemoveChildView(state_passwords_.get()); + RemoveChildView(state_history_.get()); + RemoveChildView(state_cookies_.get()); + RemoveChildView(label_bookmarks_.get()); + RemoveChildView(label_searches_.get()); + RemoveChildView(label_passwords_.get()); + RemoveChildView(label_history_.get()); + RemoveChildView(label_cookies_.get()); +} + +//////////////////////////////////////////////////////////////////////////////// +// ImportingProgressView, ImporterObserver implementation: + +void ImportingProgressView::ImportItemStarted(ImportItem item) { + DCHECK(items_ & item); + switch (item) { + case FAVORITES: + state_bookmarks_->Start(); + break; + case SEARCH_ENGINES: + state_searches_->Start(); + break; + case PASSWORDS: + state_passwords_->Start(); + break; + case HISTORY: + state_history_->Start(); + break; + case COOKIES: + state_cookies_->Start(); + break; + } +} + +void ImportingProgressView::ImportItemEnded(ImportItem item) { + DCHECK(items_ & item); + switch (item) { + case FAVORITES: + state_bookmarks_->Stop(); + state_bookmarks_->SetChecked(true); + break; + case SEARCH_ENGINES: + state_searches_->Stop(); + state_searches_->SetChecked(true); + break; + case PASSWORDS: + state_passwords_->Stop(); + state_passwords_->SetChecked(true); + break; + case HISTORY: + state_history_->Stop(); + state_history_->SetChecked(true); + break; + case COOKIES: + state_cookies_->Stop(); + state_cookies_->SetChecked(true); + break; + } +} + +void ImportingProgressView::ImportStarted() { + importing_ = true; +} + +void ImportingProgressView::ImportEnded() { + // This can happen because: + // - the import completed successfully. + // - the import was canceled by the user. + // - the user chose to skip the import because they didn't want to shut down + // Firefox. + // In every case, we need to close the UI now. + importing_ = false; + coordinator_->SetObserver(NULL); + window_->Close(); + if (import_observer_) + import_observer_->ImportComplete(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ImportingProgressView, ChromeViews::View overrides: + +void ImportingProgressView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_IMPORTPROGRESS_DIALOG_WIDTH_CHARS, + IDS_IMPORTPROGRESS_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void ImportingProgressView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (is_add && child == this) + InitControlLayout(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ImportingProgressView, ChromeViews::DialogDelegate implementation: + +int ImportingProgressView::GetDialogButtons() const { + return DIALOGBUTTON_CANCEL; +} + +std::wstring ImportingProgressView::GetDialogButtonLabel( + DialogButton button) const { + DCHECK(button == DIALOGBUTTON_CANCEL); + return l10n_util::GetString(IDS_IMPORT_PROGRESS_STATUS_CANCEL); +} + +bool ImportingProgressView::IsModal() const { + return parent_window_ != NULL; +} + +std::wstring ImportingProgressView::GetWindowTitle() const { + return l10n_util::GetString(IDS_IMPORT_PROGRESS_TITLE); +} + +bool ImportingProgressView::Cancel() { + if (!importing_) + return true; + + coordinator_->Cancel(); + // Return false because the window needs to live long enough to receive + // ImportEnded, which will close the window. + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// ImportingProgressView, private: + +void ImportingProgressView::InitControlLayout() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + CSize ps; + state_history_->GetPreferredSize(&ps); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + const int double_column_view_set_id = 1; + column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddPaddingColumn(0, kUnrelatedControlLargeHorizontalSpacing); + column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, + GridLayout::FIXED, ps.cx, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kUnrelatedControlLargeHorizontalSpacing); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(label_info_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + if (items_ & FAVORITES) { + layout->StartRow(0, double_column_view_set_id); + layout->AddView(state_bookmarks_.get()); + layout->AddView(label_bookmarks_.get()); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } + if (items_ & SEARCH_ENGINES) { + layout->StartRow(0, double_column_view_set_id); + layout->AddView(state_searches_.get()); + layout->AddView(label_searches_.get()); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } + if (items_ & PASSWORDS) { + layout->StartRow(0, double_column_view_set_id); + layout->AddView(state_passwords_.get()); + layout->AddView(label_passwords_.get()); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } + if (items_ & HISTORY) { + layout->StartRow(0, double_column_view_set_id); + layout->AddView(state_history_.get()); + layout->AddView(label_history_.get()); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } + if (items_ & COOKIES) { + layout->StartRow(0, double_column_view_set_id); + layout->AddView(state_cookies_.get()); + layout->AddView(label_cookies_.get()); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// StartImportingWithUI + +void StartImportingWithUI(HWND parent_window, + int16 items, + ImporterHost* coordinator, + const ProfileInfo& source_profile, + Profile* target_profile, + ImportObserver* observer, + bool first_run) { + DCHECK(items != 0); + ImportingProgressView* v = new ImportingProgressView( + source_profile.description, items, coordinator, observer, parent_window); + ChromeViews::Window* window = ChromeViews::Window::CreateChromeWindow( + parent_window, gfx::Rect(), v, v); + v->set_window(window); + window->Show(); + coordinator->StartImportSettings(source_profile, items, + new ProfileWriter(target_profile), + first_run); +} diff --git a/chrome/browser/views/importing_progress_view.h b/chrome/browser/views/importing_progress_view.h new file mode 100644 index 0000000..c3a560d --- /dev/null +++ b/chrome/browser/views/importing_progress_view.h @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_IMPORTING_PROGRESS_VIEW_H_ +#define CHROME_BROWSER_VIEWS_IMPORTING_PROGRESS_VIEW_H_ + +#include "chrome/browser/importer.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" + +namespace ChromeViews { +class CheckmarkThrobber; +class Label; +} + +class ImportingProgressView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ImporterHost::Observer { + public: + ImportingProgressView(const std::wstring& source_name, + int16 items, + ImporterHost* coordinator, + ImportObserver* observer, + HWND parent_window); + virtual ~ImportingProgressView(); + + void set_window(ChromeViews::Window* window) { window_ = window; } + + protected: + // Overridden from ImporterHost::Observer: + virtual void ImportItemStarted(ImportItem item); + virtual void ImportItemEnded(ImportItem item); + virtual void ImportStarted(); + virtual void ImportEnded(); + + // Overridden from ChromeViews::DialogDelegate: + virtual int GetDialogButtons() const; + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool IsModal() const; + virtual std::wstring GetWindowTitle() const; + virtual bool Cancel(); + + // Overridden from ChromeViews::View: + virtual void GetPreferredSize(CSize *out); + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Set up the control layout within this dialog. + void InitControlLayout(); + + // Various dialog controls. + scoped_ptr<ChromeViews::CheckmarkThrobber> state_bookmarks_; + scoped_ptr<ChromeViews::CheckmarkThrobber> state_searches_; + scoped_ptr<ChromeViews::CheckmarkThrobber> state_passwords_; + scoped_ptr<ChromeViews::CheckmarkThrobber> state_history_; + scoped_ptr<ChromeViews::CheckmarkThrobber> state_cookies_; + ChromeViews::Label* label_info_; + scoped_ptr<ChromeViews::Label> label_bookmarks_; + scoped_ptr<ChromeViews::Label> label_searches_; + scoped_ptr<ChromeViews::Label> label_passwords_; + scoped_ptr<ChromeViews::Label> label_history_; + scoped_ptr<ChromeViews::Label> label_cookies_; + + // The window that contains us. + ChromeViews::Window* window_; + + // The native window that we are parented to. Can be NULL. + HWND parent_window_; + + // The importer host coordinating the import. + scoped_refptr<ImporterHost> coordinator_; + + // An object that wants to be notified when the import is complete. + ImportObserver* import_observer_; + + // The ImportItems we are importing. + int16 items_; + + // True if the import operation is in progress. + bool importing_; + + DISALLOW_EVIL_CONSTRUCTORS(ImportingProgressView); +}; + +#endif // CHROME_BROWSER_VIEWS_IMPORTING_PROGRESS_VIEW_H_ diff --git a/chrome/browser/views/info_bar_alternate_nav_url_view.cc b/chrome/browser/views/info_bar_alternate_nav_url_view.cc new file mode 100644 index 0000000..74229ed --- /dev/null +++ b/chrome/browser/views/info_bar_alternate_nav_url_view.cc @@ -0,0 +1,94 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/info_bar_alternate_nav_url_view.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/web_contents.h" +#include "chrome/browser/views/event_utils.h" +#include "chrome/browser/views/info_bar_view.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/label.h" + +#include "generated_resources.h" + +InfoBarAlternateNavURLView::InfoBarAlternateNavURLView( + const std::wstring& alternate_nav_url) + : alternate_nav_url_(alternate_nav_url) { + size_t offset; + const std::wstring label(l10n_util::GetStringF( + IDS_ALTERNATE_NAV_URL_VIEW_LABEL, std::wstring(), &offset)); + DCHECK(offset != std::wstring::npos); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + if (offset > 0) { + ChromeViews::Label* label_1 = + new ChromeViews::Label(label.substr(0, offset)); + label_1->SetFont(rb.GetFont(ResourceBundle::MediumFont)); + AddChildViewLeading(label_1, 0); + } + + ChromeViews::Link* link = new ChromeViews::Link(alternate_nav_url_); + link->SetFont(rb.GetFont(ResourceBundle::MediumFont)); + link->SetController(this); + AddChildViewLeading(link, 0); + + if (offset < label.length()) { + ChromeViews::Label* label_2 = new ChromeViews::Label(label.substr(offset)); + label_2->SetFont(rb.GetFont(ResourceBundle::MediumFont)); + AddChildViewLeading(label_2, 0); + } + + SetIcon(*rb.GetBitmapNamed(IDR_INFOBAR_ALT_NAV_URL)); +} + +void InfoBarAlternateNavURLView::LinkActivated(ChromeViews::Link* source, + int event_flags) { + // Navigating may or may not automatically close the infobar, depending on + // whether the desired disposition replaces the current tab. We always want + // the bar to close, so we close it ourselves before navigating (doing things + // in the other order would be problematic if navigation synchronously closed + // the bar, as on return from the call, |this| would not exist, and calling + // Close() would corrupt memory). This means we need to save off all members + // we need before calling Close(), which destroys us. + PageNavigator* const navigator = + static_cast<InfoBarView*>(GetParent())->web_contents(); + const GURL gurl(alternate_nav_url_); + + BeginClose(); + + navigator->OpenURL(gurl, event_utils::DispositionFromEventFlags(event_flags), + // Pretend the user typed this URL, so that navigating to + // it will be the default action when it's typed again in + // the future. + PageTransition::TYPED); +} diff --git a/chrome/browser/views/info_bar_alternate_nav_url_view.h b/chrome/browser/views/info_bar_alternate_nav_url_view.h new file mode 100644 index 0000000..9dd718d --- /dev/null +++ b/chrome/browser/views/info_bar_alternate_nav_url_view.h @@ -0,0 +1,51 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BAR_ALTERNATE_NAV_URL_VIEW_H__ +#define CHROME_BROWSER_VIEWS_INFO_BAR_ALTERNATE_NAV_URL_VIEW_H__ + +#include "chrome/browser/views/info_bar_item_view.h" +#include "chrome/views/link.h" + +class InfoBarAlternateNavURLView : public InfoBarItemView, + public ChromeViews::LinkController { + public: + explicit InfoBarAlternateNavURLView(const std::wstring& alternate_nav_url); + virtual ~InfoBarAlternateNavURLView() { } + + // LinkController + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + private: + std::wstring alternate_nav_url_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBarAlternateNavURLView); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BAR_ALTERNATE_NAV_URL_VIEW_H__
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_confirm_view.cc b/chrome/browser/views/info_bar_confirm_view.cc new file mode 100644 index 0000000..8af6a1b --- /dev/null +++ b/chrome/browser/views/info_bar_confirm_view.cc @@ -0,0 +1,124 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/info_bar_confirm_view.h" +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +InfoBarConfirmView::InfoBarConfirmView(const std::wstring& message) + : ok_button_(NULL), + cancel_button_(NULL), + InfoBarMessageView(message) { + Init(); +} + +InfoBarConfirmView::~InfoBarConfirmView() {} + +void InfoBarConfirmView::OKButtonPressed() { + // Delete and close this view by default. + BeginClose(); +} + +void InfoBarConfirmView::CancelButtonPressed() { + // Delete and close this view by default. + BeginClose(); +} + +void InfoBarConfirmView::ButtonPressed(ChromeViews::NativeButton* sender) { + // If you close the bar from one of these functions, make sure to use + // BeginClose() - Close() could delete us and cause the rest of the + // function to go bananas. + if (sender == ok_button_) + OKButtonPressed(); + else if (sender == cancel_button_) + CancelButtonPressed(); + + // Disable our buttons - we only want to allow users to press one, and + // leaving them enabled could allow further interaction during the close + // animation. + if (ok_button_) + ok_button_->SetEnabled(false); + if (cancel_button_) + cancel_button_->SetEnabled(false); +} + +void InfoBarConfirmView::SetOKButtonLabel(const std::wstring& label) { + if (ok_button_) { + ok_button_->SetLabel(label); + ok_button_->SetAccessibleName(label); + Layout(); + } +} + +void InfoBarConfirmView::SetCancelButtonLabel(const std::wstring& label) { + if (cancel_button_) { + cancel_button_->SetLabel(label); + cancel_button_->SetAccessibleName(label); + Layout(); + } +} + +void InfoBarConfirmView::RemoveCancelButton() { + if (cancel_button_) { + RemoveChildView(cancel_button_); + delete cancel_button_; + cancel_button_ = NULL; + Layout(); + } +} + +void InfoBarConfirmView::RemoveOKButton() { + if (ok_button_) { + RemoveChildView(ok_button_); + delete ok_button_; + ok_button_ = NULL; + Layout(); + } +} + +bool InfoBarConfirmView::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_GROUPING; + return true; +} + +void InfoBarConfirmView::Init() { + ok_button_ = new ChromeViews::NativeButton(l10n_util::GetString(IDS_OK)); + ok_button_->SetListener(this); + + cancel_button_ = + new ChromeViews::NativeButton(l10n_util::GetString(IDS_CANCEL)); + cancel_button_->SetListener(this); + AddChildViewTrailing(cancel_button_, kRelatedButtonHSpacing); + AddChildViewTrailing(ok_button_); +}
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_confirm_view.h b/chrome/browser/views/info_bar_confirm_view.h new file mode 100644 index 0000000..de0a1a8 --- /dev/null +++ b/chrome/browser/views/info_bar_confirm_view.h @@ -0,0 +1,89 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BAR_CONFIRM_VIEW_H__ +#define CHROME_BROWSER_VIEWS_INFO_BAR_CONFIRM_VIEW_H__ + +#include "chrome/browser/views/info_bar_message_view.h" +#include "chrome/views/native_button.h" + +// An info bar with a message, two buttons (labeled OK and Cancel by +// default), and a close button. Can be inherited to override the behavior +// of button presses. +class InfoBarConfirmView : public InfoBarMessageView, + public ChromeViews::NativeButton::Listener { + public: + explicit InfoBarConfirmView(const std::wstring& message); + + virtual ~InfoBarConfirmView(); + + // Invoked when the OK button is pressed. Closes info bar by default. + virtual void OKButtonPressed(); + + // Invoked when the Cancel button is pressed. Closes info bar by default. + virtual void CancelButtonPressed(); + + // ButtonListener Method: + // Invokes OKButtonPressed or CancelButtonPressed() when their + // respective buttons are pressed. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Sets the label on the OK button, if it exists. + void SetOKButtonLabel(const std::wstring& label); + + // Sets the label on the Cancel button, if it exists. + void SetCancelButtonLabel(const std::wstring& label); + + // Removes the cancel button from the info bar. + // Can't be re-added. + void RemoveCancelButton(); + + // Removes the OK button from the info bar. + // Can't be re-added. + void RemoveOKButton(); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + private: + // Creates the ok and cancel buttons. And then calls the InfoBarMessageViews + // init to set up the message and close buttons which will + // then call AddAllChildViews. + void Init(); + + ChromeViews::NativeButton* ok_button_; + + ChromeViews::NativeButton* cancel_button_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBarConfirmView); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BAR_CONFIRM_VIEW_H__
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_item_view.cc b/chrome/browser/views/info_bar_item_view.cc new file mode 100644 index 0000000..99740f7 --- /dev/null +++ b/chrome/browser/views/info_bar_item_view.cc @@ -0,0 +1,317 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/info_bar_item_view.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/external_focus_tracker.h" +#include "chrome/views/image_view.h" +#include "chrome/views/root_view.h" + +#include "generated_resources.h" + +namespace { + +class HorizontalSpacer : public ChromeViews::View { + public: + explicit HorizontalSpacer(int width) : width_(width) {} + + void GetPreferredSize(CSize* out) { + out->cx = width_; + out->cy = 0; + } + + private: + int width_; +}; + +const int kInfoBarVerticalSpacing = 3; +const int kInfoBarLeftMargin = 3; +const double kInfoBarHeight = 37.0; + +} // namespace + +InfoBarItemView::InfoBarItemView() + : insert_index_(0), + close_button_(NULL), + icon_(NULL) { + Init(); +} + +InfoBarItemView::~InfoBarItemView() { +} + +// static +int InfoBarItemView::CenterPosition(int size, int target_size) { + return (target_size - size) / 2; +} + +void InfoBarItemView::GetPreferredSize(CSize* out) { + out->cx = GetParent()->GetWidth(); + out->cy = static_cast<int>(kInfoBarHeight * animation_->GetCurrentValue()); +} + +// The following is an overall note on the underlying implementation. You don't +// need this in order to use this view. Ignore unless you're editing +// implementation: +// Layout() lays out all of its child views, but it uses insert_index_ to +// decide whether to lay out on the left or right. Whenever a view is added or +// removed the insert_index_ is updated accordingly to make sure it is directly +// between left aligned views and right aligned views. Whenever a view is added, +// a spacer view provides padding to the right of the view if the view is +// left aligned, or to the left of the view if the view is right aligned. +// Removing assumes this spacer view exists. +// +// For example, below M stands for built in margins, I stands for the icon +// which is optional and includes padding of its own. L stands for a left +// aligned view, and R for a right aligned view. P is padding, which can be +// zero. The insert index is currently 4, separating the right of left views. +// The numbers represent what index the child views P, R, and L occupy. +// +// M I L P L P P R P R M +// 0 1 2 3 ^4 5 6 7 +// Say we call AddChildViewTrailing(right_view, 10). We end up with: +// M I L P L P P R P R P R M +// 0 1 2 3 ^4 5 6 7 8 9 +// First the right view was added, then its padding was added, the insert index +// did not need to change because it still separates the right and left views. +// Note that the padding showed up at the lower index, or to the left of the +// right aligned view. +// Then we call AddChildViewLeading(left_view, 0). We end up with: +// M I L P L P L P P R P R P R M +// 0 1 2 3 4 5 ^6 7 8 9 10 11 +// First the left view was added, then the insert_index_ was incremented, then +// the padding is added, even though it is zero (It has no effect on layout) +// and insert_index_ is incremented again to keep it between the right and +// left views. Note in this case, the padding appears to the right of the view +// left aligned view. Removing works the same, but in reverse. +void InfoBarItemView::Layout() { + const int width = GetWidth(); + const int height = GetHeight(); + + int next_x = width - kButtonHEdgeMargin; + int height_diff = static_cast<int>(kInfoBarHeight) - height; + const int child_count = GetChildViewCount(); + // Anything greater than or equal to insert_index_ is laid out on the right, + // with the greatest index (the first one added to the right) being laid out + // rightmost. + for (int i = child_count - 1; i >= insert_index_ ; i--) { + View* v = GetChildViewAt(i); + if (v->IsVisible()) { + CSize view_size; + v->GetPreferredSize(&view_size); + next_x = next_x - view_size.cx; + v->SetBounds(next_x, + CenterPosition(view_size.cy, + static_cast<int>(kInfoBarHeight)) - height_diff, + view_size.cx, + view_size.cy); + } + } + int left_most_x = next_x; + + next_x = kInfoBarLeftMargin; + + // Anything less than insert_index_ is laid out on the left, with the + // smallest index (the first one added to the left) being laid out leftmost. + for (int i = 0; i < insert_index_ ; i++) { + View* v = GetChildViewAt(i); + if (v->IsVisible()) { + CSize view_size; + v->GetPreferredSize(&view_size); + int remaining_space = std::max(0, left_most_x - next_x); + if (view_size.cx > remaining_space) { + view_size.cx = remaining_space; + } + v->SetBounds(next_x, + CenterPosition(view_size.cy, + static_cast<int>(kInfoBarHeight)) - height_diff, + view_size.cx, + view_size.cy); + next_x = next_x + view_size.cx; + } + } +} + +void InfoBarItemView::DidChangeBounds(const CRect& previous, + const CRect& current) { + if (GetParent() != NULL) + Layout(); +} + +void InfoBarItemView::BeginClose() { + animation_->Hide(); +} + +void InfoBarItemView::Close() { + ChromeViews::View* parent = GetParent(); + parent->RemoveChildView(this); + if (focus_tracker_.get() != NULL) + focus_tracker_->FocusLastFocusedExternalView(); + delete this; +} + +void InfoBarItemView::CloseButtonPressed() { + // Close this view by default. + BeginClose(); +} + +void InfoBarItemView::AddChildViewTrailing(ChromeViews::View* view, + int leading_padding) { + ChromeViews::View::AddChildView(insert_index_, view); + View* padding = new HorizontalSpacer(leading_padding); + ChromeViews::View::AddChildView(insert_index_, padding); +} + +void InfoBarItemView::AddChildViewTrailing(ChromeViews::View* view) { + AddChildViewTrailing(view, kUnrelatedControlHorizontalSpacing); +} + +void InfoBarItemView::AddChildViewLeading(ChromeViews::View* view, + int trailing_padding) { + ChromeViews::View::AddChildView(insert_index_, view); + insert_index_++; + View* padding = new HorizontalSpacer(trailing_padding); + ChromeViews::View::AddChildView(insert_index_, padding); + insert_index_++; +} + +void InfoBarItemView::AddChildViewLeading(ChromeViews::View* view) { + AddChildViewLeading(view, kRelatedControlSmallHorizontalSpacing); +} + +void InfoBarItemView::SetIcon(const SkBitmap& icon) { + if (icon_ == NULL) { + // Add the icon and its padding to the far left of the info bar, and adjust + // the insert index accordingly. + icon_ = new ChromeViews::ImageView(); + View* padding = new HorizontalSpacer(kRelatedControlHorizontalSpacing); + ChromeViews::View::AddChildView(0, padding); + ChromeViews::View::AddChildView(0, icon_); + insert_index_ += 2; + } + icon_->SetImage(icon); + Layout(); +} + +void InfoBarItemView::ViewHierarchyChanged(bool is_add, + View *parent, + View *child) { + if (child == this) { + if (is_add) { + Layout(); + + View* root_view = GetRootView(); + HWND root_hwnd = NULL; + if (root_view) + root_hwnd = root_view->GetViewContainer()->GetHWND(); + + if (root_hwnd) { + focus_tracker_.reset(new ChromeViews::ExternalFocusTracker( + this, ChromeViews::FocusManager::GetFocusManager(root_hwnd))); + } + } else { + // When we're removed from the hierarchy our focus manager is no longer + // valid. + if (focus_tracker_.get() != NULL) + focus_tracker_->SetFocusManager(NULL); + } + } +} + +void InfoBarItemView::AddChildView(ChromeViews::View* view) { + AddChildViewTrailing(view, kUnrelatedControlHorizontalSpacing); +} + +void InfoBarItemView::AddChildView(int index, ChromeViews::View* view) { + if (index < insert_index_) + AddChildViewLeading(view); + else + AddChildViewTrailing(view); +} + +void InfoBarItemView::RemoveChildView(ChromeViews::View* view) { + int index = GetChildIndex(view); + if (index >= 0) { + if (index < insert_index_) { + // We're removing a leading view. So the view at index + 1 (immediately + // trailing) is the corresponding spacer view. + View* spacer_view = GetChildViewAt(index + 1); + ChromeViews::View::RemoveChildView(view); + ChromeViews::View::RemoveChildView(spacer_view); + delete spacer_view; + // Need to change the insert_index_ so it is still pointing at the + // "middle" index between left and right aligned views. + insert_index_ -= 2; + } else { + // We're removing a trailing view. So the view at index - 1 (immediately + // leading) is the corresponding spacer view. + View* spacer_view = GetChildViewAt(index - 1); + ChromeViews::View::RemoveChildView(view); + ChromeViews::View::RemoveChildView(spacer_view); + delete spacer_view; + } + } +} + +void InfoBarItemView::ButtonPressed(ChromeViews::BaseButton* button) { + if (button == close_button_) + CloseButtonPressed(); +} + +void InfoBarItemView::AnimationProgressed(const Animation* animation) { + static_cast<InfoBarView*>(GetParent())->ChildAnimationProgressed(); +} + +void InfoBarItemView::AnimationEnded(const Animation* animation) { + static_cast<InfoBarView*>(GetParent())->ChildAnimationEnded(); + + if (!animation_->IsShowing()) + Close(); +} + +void InfoBarItemView::Init() { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + close_button_ = new ChromeViews::Button(); + close_button_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_CLOSE_BAR)); + close_button_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_CLOSE_BAR_H)); + close_button_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_CLOSE_BAR_P)); + close_button_->SetListener(this, 0); + close_button_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_CLOSE)); + AddChildViewTrailing(close_button_); + + animation_.reset(new SlideAnimation(this)); + animation_->SetTweenType(SlideAnimation::NONE); + animation_->Show(); +} diff --git a/chrome/browser/views/info_bar_item_view.h b/chrome/browser/views/info_bar_item_view.h new file mode 100644 index 0000000..e4fa4d2 --- /dev/null +++ b/chrome/browser/views/info_bar_item_view.h @@ -0,0 +1,163 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BAR_ITEM_VIEW_H__ +#define CHROME_BROWSER_VIEWS_INFO_BAR_ITEM_VIEW_H__ + +#include "chrome/browser/views/info_bar_view.h" +#include "chrome/common/slide_animation.h" +#include "chrome/views/button.h" + +namespace ChromeViews { + class ExternalFocusTracker; + class ImageView; +} +// Note: An InfoBarItemView must be added as a child of InfoBarView to be +// displayed correctly. +// +// InfoBarItemView is basically a view container that lays out views in a +// horizontal row, on either the left or the right, with specified padding. It +// has a close button on the far right, which can't be removed, and closes the +// info bar by default. An icon can be set to be displayed leading all other +// views by calling SetIcon(). +// +// A view can be added to either the left or the right of the info bar by +// calling AddChildViewTrailing and AddChildViewLeading. +// +// The most recently added views to either side will always be located further +// towards the center than views added less recently, with the first views added +// to the left or right being located on the leftmost or rightmost sides of the +// info bar, respectively. Each view has a default spacing from the next +// view added to that side, but you can edit that by specifying a padding when +// you add a view. For example, if you add a view to the left with a padding of +// 6 specified, it will be placed in the leftmost position, and the next view +// added to the left will be 6 pixels to the right of the previously added view. +class InfoBarItemView : public ChromeViews::View, + public ChromeViews::BaseButton::ButtonListener, + public AnimationDelegate { + public: + InfoBarItemView(); + + virtual ~InfoBarItemView(); + + // The preferred height is equal to the maximum height of all views + // in the info bar. Preferred width is equal to the parents width. + virtual void GetPreferredSize(CSize* out); + + // Lays out all child views of the info bar from trailing to leading. + virtual void Layout(); + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Starts the close animation, which will end in the bar closing itself. + void BeginClose(); + + // Removes this InfoBarItem from its parent view and then deletes it. + void Close(); + + // ButtonListener Method + // Calls CloseButtonPressed() when the close button is pressed + virtual void ButtonPressed(ChromeViews::BaseButton* button); + + // Adds |view| to the info bar, directly leading the last trailing view + // added, according to that views specified padding. The next trailing view + // added will in turn be leading this view by |leading_padding| pixels. + // Specify 0 for |leading_padding| if the views should be flush. + void AddChildViewTrailing(ChromeViews::View* view, int leading_padding); + + // Calls AddChildViewTrailing with a default amount of padding. + void AddChildViewTrailing(ChromeViews::View* view); + + // Adds |view| to the info bar, directly trailing the last leading view + // added, according to that views specified padding. The next leading view + // added will in turn be trailing this view by |trailing_padding| pixels. + // Specify 0 for |trailing_padding| if the views should be flush. + void AddChildViewLeading(ChromeViews::View* view, int trailing_padding); + + // Calls AddChildViewLeading with a default amount of padding. + void AddChildViewLeading(ChromeViews::View* view); + + // Sets the icon to be displayed leading all other views in the info bar. + // The icon will be displayed at its images height and width by default. + void SetIcon(const SkBitmap& icon); + + protected: + // Returns the desired position for a centered object of size |size| within + // a region of size |target_size|. + static int CenterPosition(int size, int target_size); + + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Overridden from the basic Views AddChildView. Calls + // AddChildViewTrailing(view) + virtual void AddChildView(ChromeViews::View* view); + + // Overridden from basic View. Adds the view to the same side as the view + // at index. Does *not* insert at the specified index, or even neccesarily + // close to it. + virtual void AddChildView(int index, ChromeViews::View* view); + + // Overridden from the basic Views AddChildView, removes the specified view + // as well as its padding. + virtual void RemoveChildView(ChromeViews::View* view); + + // Invoked whenever the close button is pressed. Closes infobar by default. + virtual void CloseButtonPressed(); + + private: + // Creates cancel button. + void Init(); + + // SlideAnimationDelegate implementation. + virtual void AnimationProgressed(const Animation* animation); + virtual void AnimationEnded(const Animation* animation); + + scoped_ptr<SlideAnimation> animation_; + + // View index where all new views will be inserted. Any view at an index less + // than insert_index will be laid out will be leading views (left aligned in + // left to right languages), any view greater than or equal to insert_index_ + // will be laid out trailing (right aligned in left to right languages). + int insert_index_; + + // Dismisses the info bar by default. + ChromeViews::Button* close_button_; + + // Optional icon to be displayed at the far left of the infobar. + ChromeViews::ImageView* icon_; + + // Tracks and stores the last focused view which is not the InfoBarItemView or + // any of its children. Used to restore focus once the InfoBarItemView is + // closed. + scoped_ptr<ChromeViews::ExternalFocusTracker> focus_tracker_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBarItemView); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BAR_ITEM_VIEW_H__ diff --git a/chrome/browser/views/info_bar_message_view.cc b/chrome/browser/views/info_bar_message_view.cc new file mode 100644 index 0000000..766796a --- /dev/null +++ b/chrome/browser/views/info_bar_message_view.cc @@ -0,0 +1,67 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/info_bar_message_view.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/label.h" + +InfoBarMessageView::InfoBarMessageView(const std::wstring& message) + : message_string_(message), + message_label_(NULL) { + Init(); +} + +InfoBarMessageView::InfoBarMessageView(ChromeViews::Label* message) + : message_string_(), + message_label_(message) { + Init(); +} + +InfoBarMessageView::~InfoBarMessageView() {} + +void InfoBarMessageView::SetMessageText(const std::wstring& message) { + message_label_->SetText(message); + Layout(); +} + +std::wstring InfoBarMessageView::GetMessageText() { + return message_label_->GetText(); +} + +void InfoBarMessageView::Init() { + if (message_label_ == NULL) { + message_label_ = new ChromeViews::Label(message_string_); + message_label_->SetFont( + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont)); + } else { + message_string_ = message_label_->GetText(); + } + + AddChildViewLeading(message_label_); +}
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_message_view.h b/chrome/browser/views/info_bar_message_view.h new file mode 100644 index 0000000..bbd3c51 --- /dev/null +++ b/chrome/browser/views/info_bar_message_view.h @@ -0,0 +1,63 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BAR_MESSAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_INFO_BAR_MESSAGE_VIEW_H__ + +#include "chrome/browser/views/info_bar_item_view.h" +#include "chrome/views/label.h" + +// A generic message for the info bar. Displays a label and a close button. +// Can be inherited to override the default behavior of the close button, which +// closes and deletes the info bar by default. +class InfoBarMessageView : public InfoBarItemView { + + public: + explicit InfoBarMessageView(const std::wstring& message); + + explicit InfoBarMessageView(ChromeViews::Label* message); + + virtual ~InfoBarMessageView(); + + void SetMessageText(const std::wstring& message); + + std::wstring GetMessageText(); + + private: + // Creates message label. + void Init(); + + std::wstring message_string_; + + ChromeViews::Label* message_label_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBarMessageView); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BAR_MESSAGE_VIEW_H__
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_view.cc b/chrome/browser/views/info_bar_view.cc new file mode 100644 index 0000000..399a120 --- /dev/null +++ b/chrome/browser/views/info_bar_view.cc @@ -0,0 +1,245 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/info_bar_view.h" + +#include "base/logging.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/tab_contents_delegate.h" +#include "chrome/browser/web_contents.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" + +#include "generated_resources.h" + +// Color for the separator. +static const SkColor kSeparatorColor = SkColorSetRGB(165, 165, 165); + +// Default background color for the info bar. +static const SkColor kBackgroundColorTop = SkColorSetRGB(255, 242, 183); +static const SkColor kBackgroundColorBottom = SkColorSetRGB(250, 230, 145); + +static const SkColor kBorderColorTop = SkColorSetRGB(240, 230, 170); +static const SkColor kBorderColorBottom = SkColorSetRGB(236, 216, 133); + +// Height of the separator. +static const int kSeparatorHeight = 1; + +InfoBarView::InfoBarView(WebContents* web_contents) + : web_contents_(web_contents) { + Init(); +} + +InfoBarView::~InfoBarView() {} + +void InfoBarView::AppendInfoBarItem(ChromeViews::View* view, bool auto_expire) { + // AddChildView adds an entry to expire_map_ for view. + AddChildView(view); + if (auto_expire) + expire_map_[view] = GetActiveID(); + else + expire_map_.erase(expire_map_.find(view)); +} + +// Preferred size is equal to the max of the childrens horizontal sizes +// and the sum of their vertical sizes. +void InfoBarView::GetPreferredSize(CSize *out) { + out->cx = 0; + out->cy = 0; + + // We count backwards so the most recently added view is on the top. + for (int i = GetChildViewCount() - 1; i >= 0; i--) { + View* v = GetChildViewAt(i); + if (v->IsVisible()) { + CSize view_size; + v->GetPreferredSize(&view_size); + out->cx = std::max(static_cast<int>(out->cx), v->GetWidth()); + out->cy += static_cast<int>(view_size.cy) + kSeparatorHeight; + } + } +} + +void InfoBarView::Layout() { + int width = GetWidth(); + int height = GetHeight(); + + int x = 0; + int y = height; + + // We lay the bars out from bottom to top. + for (int i = 0; i < GetChildViewCount(); ++i) { + View* v = GetChildViewAt(i); + if (!v->IsVisible()) + continue; + + CSize view_size; + v->GetPreferredSize(&view_size); + int view_width = std::max(static_cast<int>(view_size.cx), width); + y = y - view_size.cy - kSeparatorHeight; + v->SetBounds(x, + y, + view_width, + view_size.cy); + } +} + +void InfoBarView::Paint(ChromeCanvas* canvas) { + PaintBackground(canvas); + PaintBorder(canvas); + PaintSeparators(canvas); +} + +void InfoBarView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void InfoBarView::ChildAnimationProgressed() { + if (web_contents_) + web_contents_->ToolbarSizeChanged(true); +} + +void InfoBarView::ChildAnimationEnded() { + if (web_contents_) + web_contents_->ToolbarSizeChanged(false); +} + +void InfoBarView::DidNavigate(NavigationEntry* entry) { + // Determine the views to remove first. + std::vector<ChromeViews::View*> to_remove; + NavigationEntry* pending_entry = + web_contents_->controller()->GetPendingEntry(); + const int active_id = pending_entry ? pending_entry->unique_id() : + entry->unique_id(); + for (std::map<View*,int>::iterator i = expire_map_.begin(); + i != expire_map_.end(); ++i) { + if ((i->second) != active_id) + to_remove.push_back(i->first); + } + + if (to_remove.empty()) + return; + + // Remove the views. + for (std::vector<ChromeViews::View*>::iterator i = to_remove.begin(); + i != to_remove.end(); ++i) { + // RemoveChildView takes care of removing from expire_map for us. + RemoveChildView(*i); + + // We own the child and we're removing it, need to delete it. + delete *i; + } + + if (GetChildViewCount() == 0) { + // All our views have been removed, no need to stay visible. + web_contents_->SetInfoBarVisible(false); + } else if (web_contents_) { + // This triggers a layout. + web_contents_->ToolbarSizeChanged(false); + } +} + +void InfoBarView::ViewHierarchyChanged(bool is_add, View *parent, + View *child) { + if (parent == this && child->GetParent() == this) { + // ViewHierarchyChanged is actually called before a child is removed. + // So set child to not be visible so it isn't included in the layout. + if (!is_add) { + child->SetVisible(false); + + // If there's an entry in the expire map, nuke it. + std::map<View*,int>::iterator i = expire_map_.find(child); + if (i != expire_map_.end()) + expire_map_.erase(i); + } else { + expire_map_[child] = GetActiveID(); + } + + if (web_contents_->IsInfoBarVisible()) { + web_contents_->ToolbarSizeChanged(false); + } else { + web_contents_->SetInfoBarVisible(true); + } + } +} + +void InfoBarView::Init() { + SetBackground( + ChromeViews::Background::CreateVerticalGradientBackground( + kBackgroundColorTop, kBackgroundColorBottom)); +} + +int InfoBarView::GetActiveID() const { + if (!web_contents_) + return 0; + + // The WebContents is guaranteed to have a controller. + const NavigationEntry* const entry = + web_contents_->controller()->GetActiveEntry(); + return entry ? entry->unique_id() : 0; +} + +void InfoBarView::PaintBorder(ChromeCanvas* canvas) { + canvas->FillRectInt(kBorderColorTop, 0, 0, GetWidth(), 1); + canvas->FillRectInt(kBorderColorBottom, + 0, GetHeight() - kSeparatorHeight - 1, + GetWidth(), kSeparatorHeight); + + if (GetChildViewCount() > 0) + canvas->FillRectInt(kSeparatorColor, 0, GetHeight() - kSeparatorHeight, + GetWidth(), kSeparatorHeight); +} + +void InfoBarView::PaintSeparators(ChromeCanvas* canvas) { + ChromeViews::View* last_view = NULL; + for (int i = GetChildViewCount() - 1; i >= 0; i--) { + ChromeViews::View* view = GetChildViewAt(i); + if (last_view != NULL) { + if (view->IsVisible()) { + PaintSeparator(canvas, last_view, view); + } else { + // We aren't interested in views we can't see. + continue; + } + } + last_view = view; + } +} + +void InfoBarView::PaintSeparator(ChromeCanvas* canvas, + ChromeViews::View* v1, + ChromeViews::View* v2) { + canvas->FillRectInt(kSeparatorColor, + 0, + v2->GetY() - kSeparatorHeight, + GetWidth(), + kSeparatorHeight); +}
\ No newline at end of file diff --git a/chrome/browser/views/info_bar_view.h b/chrome/browser/views/info_bar_view.h new file mode 100644 index 0000000..fd23dc0 --- /dev/null +++ b/chrome/browser/views/info_bar_view.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BAR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_INFO_BAR_VIEW_H__ + +#include <map> + +#include "chrome/views/view.h" + +class NavigationEntry; +class WebContents; + +// This view is used to store and display views in the info bar. +// +// It will paint all of its children vertically, with the most recently +// added child displayed at the top of the info bar. +class InfoBarView : public ChromeViews::View { + public: + explicit InfoBarView(WebContents* web_contents); + + virtual ~InfoBarView(); + + // Adds view as a child. Views adding using AddChildView() are automatically + // removed after 1 navigation, which is also the behavior if you set + // |auto_expire| to true here. You mainly need this function if you want to + // add an infobar that should not expire. + void AppendInfoBarItem(ChromeViews::View* view, bool auto_expire); + + virtual void GetPreferredSize(CSize *out); + + virtual void Layout(); + + // API to allow infobar children to notify us of size changes. + virtual void ChildAnimationProgressed(); + virtual void ChildAnimationEnded(); + + // Invokes the following methods to do painting: + // PaintBackground, PaintBorder and PaintSeparators. + virtual void Paint(ChromeCanvas* canvas); + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + // Notification that the user has done a navigation. Removes child views that + // are set to be removed after a certain number of navigations and + // potentially hides the info bar. |entry| is the new entry that we are + // navigating to. + void DidNavigate(NavigationEntry* entry); + + WebContents* web_contents() { return web_contents_; } + + protected: + // Overridden to force the frame to re-layout the info bar whenever a view + // is added or removed. + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + private: + void Init(); + + // Returns the unique ID of the active entry on the WebContents' + // NavigationController. + int GetActiveID() const; + + // Paints the border. + void PaintBorder(ChromeCanvas* canvas); + + // Paints the separators. This invokes PaintSeparator to paint a particular + // separator. + void PaintSeparators(ChromeCanvas* canvas); + + // Paints the separator between views. + void PaintSeparator(ChromeCanvas* canvas, + ChromeViews::View* v1, + ChromeViews::View* v2); + + WebContents* web_contents_; + + // Map from view to number of navigations before it is removed. If a child + // doesn't have an entry in here, it is NOT removed on navigations. + std::map<View*,int> expire_map_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBarView); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BAR_VIEW_H__ diff --git a/chrome/browser/views/info_bubble.cc b/chrome/browser/views/info_bubble.cc new file mode 100644 index 0000000..f239684 --- /dev/null +++ b/chrome/browser/views/info_bubble.cc @@ -0,0 +1,445 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/info_bubble.h" + +#include "base/win_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/focus_manager.h" + +using ChromeViews::View; + +// All sizes are in pixels. + +// Size of the border, along each edge. +static const int kBorderSize = 1; + +// Size of the arrow. +static const int kArrowSize = 5; + +// Number of pixels to the start of the arrow from the edge of the window. +static const int kArrowXOffset = 13; + +// Number of pixels between the tip of the arrow and the region we're +// pointing to. +static const int kArrowToContentPadding = -4; + +// Background color of the bubble. +static const SkColor kBackgroundColor = SK_ColorWHITE; + +// Color of the border and arrow. +static const SkColor kBorderColor1 = SkColorSetRGB(99, 99, 99); +// Border shadow color. +static const SkColor kBorderColor2 = SkColorSetRGB(160, 160, 160); + +// Intended dimensions of the bubble's corner images. If you update these, +// make sure that the OnSize code works. +static const int kInfoBubbleCornerWidth = 3; +static const int kInfoBubbleCornerHeight = 3; + +// Bubble corner images. +static const SkBitmap* kInfoBubbleCornerTopLeft = NULL; +static const SkBitmap* kInfoBubbleCornerTopRight = NULL; +static const SkBitmap* kInfoBubbleCornerBottomLeft = NULL; +static const SkBitmap* kInfoBubbleCornerBottomRight = NULL; + +// Margins around the content. +static const int kInfoBubbleViewTopMargin = 6; +static const int kInfoBubbleViewBottomMargin = 9; +static const int kInfoBubbleViewLeftMargin = 6; +static const int kInfoBubbleViewRightMargin = 6; + +// The minimum alpha the bubble can be - because we're using a simple layered +// window (in order to get window-level alpha at the same time as using native +// controls), the window's drop shadow doesn't fade; this means if we went +// to zero alpha, you'd see a drop shadow outline against nothing. +static const int kMinimumAlpha = 72; + +// InfoBubble ----------------------------------------------------------------- + +// static +InfoBubble* InfoBubble::Show(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content, + InfoBubbleDelegate* delegate) { + InfoBubble* window = new InfoBubble(); + window->Init(parent_hwnd, position_relative_to, content); + ChromeFrame* frame = window->GetHostingFrame(); + if (frame) + frame->InfoBubbleShowing(); + window->ShowWindow(SW_SHOW); + window->delegate_ = delegate; + return window; +} + +InfoBubble::InfoBubble() : content_view_(NULL) { +} + +InfoBubble::~InfoBubble() { +} + +void InfoBubble::Init(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content) { + if (kInfoBubbleCornerTopLeft == NULL) { + kInfoBubbleCornerTopLeft = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_TOP_LEFT); + kInfoBubbleCornerTopRight = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_TOP_RIGHT); + kInfoBubbleCornerBottomLeft = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_BOTTOM_LEFT); + kInfoBubbleCornerBottomRight = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_BOTTOM_RIGHT); + } + set_window_style(WS_POPUP | WS_CLIPCHILDREN); + set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW); + content_view_ = CreateContentView(content); + gfx::Rect bounds = content_view_-> + CalculateWindowBounds(parent_hwnd, position_relative_to); + set_initial_class_style( + (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? + 0 : CS_DROPSHADOW); + + HWNDViewContainer::Init(parent_hwnd, bounds, content_view_, true); + // The preferred size may differ when parented. Ask for the bounds again + // and if they differ reset the bounds. + gfx::Rect parented_bounds = content_view_-> + CalculateWindowBounds(parent_hwnd, position_relative_to); + + // Set our initial alpha to zero so we don't flicker at the user. This + // doesn't trigger UpdateLayeredWindow, which would explode our native + // controls. + SetLayeredAlpha(kMinimumAlpha); + + if (bounds != parented_bounds) { + SetWindowPos(NULL, parented_bounds.x(), parented_bounds.y(), + parented_bounds.width(), parented_bounds.height(), + SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER); + // Invoke ChangeSize, otherwise layered window isn't updated correctly. + ChangeSize(0, CSize(parented_bounds.width(), parented_bounds.height())); + } + + // Register the Escape accelerator for closing. + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(GetHWND()); + focus_manager->RegisterAccelerator(ChromeViews::Accelerator(VK_ESCAPE, + false, false, + false), + this); + + fade_animation_.reset(new SlideAnimation(this)); + fade_animation_->Show(); +} + +void InfoBubble::Close() { + // We don't fade out because it looks terrible. + ChromeFrame* frame = GetHostingFrame(); + if (delegate_) + delegate_->InfoBubbleClosing(this); + if (frame) + frame->InfoBubbleClosing(); + HWNDViewContainer::Close(); +} + +void InfoBubble::AnimationProgressed(const Animation* animation) { + int alpha = static_cast<int>(static_cast<double> + (fade_animation_->GetCurrentValue() * (255.0 - kMinimumAlpha) + + kMinimumAlpha)); + + SetLayeredWindowAttributes(GetHWND(), + RGB(0xFF, 0xFF, 0xFF), + alpha, + LWA_ALPHA); + content_view_->SchedulePaint(); +} + +bool InfoBubble::AcceleratorPressed( + const ChromeViews::Accelerator& accelerator) { + DCHECK(accelerator.GetKeyCode() == VK_ESCAPE); + if (!delegate_ || delegate_->CloseOnEscape()) { + Close(); + return true; + } + return false; +} + +void InfoBubble::OnSize(UINT param, const CSize& size) { + SetWindowRgn(content_view_->GetMask(size), TRUE); +} + +void InfoBubble::OnActivate(UINT action, BOOL minimized, HWND window) { + // The popup should close when it is deactivated. + if (action == WA_INACTIVE) { + Close(); + } else if (action == WA_ACTIVE) { + DCHECK(GetRootView()->GetChildViewCount() > 0); + GetRootView()->GetChildViewAt(0)->RequestFocus(); + } +} + +InfoBubble::ContentView* InfoBubble::CreateContentView(View* content) { + return new ContentView(content, this); +} + +ChromeFrame* InfoBubble::GetHostingFrame() { + HWND owning_frame_hwnd = GetAncestor(GetHWND(), GA_ROOTOWNER); + ChromeFrame* frame = ChromeFrame::GetChromeFrameForWindow(owning_frame_hwnd); + if (!frame) { + // We should always have a frame, but there was a bug else where that + // made it possible for the frame to be NULL, so we have the check. If + // you hit this, file a bug. + NOTREACHED(); + } + return frame; +} + +// ContentView ---------------------------------------------------------------- + +InfoBubble::ContentView::ContentView(ChromeViews::View* content, + InfoBubble* host) + : host_(host) { + if (UILayoutIsRightToLeft()) { + arrow_edge_ = TOP_RIGHT; + } else { + arrow_edge_ = TOP_LEFT; + } + AddChildView(content); +} + +gfx::Rect InfoBubble::ContentView::CalculateWindowBounds( + HWND parent_hwnd, + const gfx::Rect& position_relative_to) { + gfx::Rect monitor_bounds = win_util::GetMonitorBoundsForRect( + position_relative_to); + // Calculate the bounds using TOP_LEFT (the default). + gfx::Rect window_bounds = CalculateWindowBounds(position_relative_to); + if (monitor_bounds.IsEmpty() || monitor_bounds.Contains(window_bounds)) + return window_bounds; + // Didn't fit, adjust the edge to fit as much as we can. + if (window_bounds.bottom() > monitor_bounds.bottom()) + SetArrowEdge(BOTTOM_LEFT); + if (window_bounds.right() > monitor_bounds.right()) { + if (IsTop()) + SetArrowEdge(TOP_RIGHT); + else + SetArrowEdge(BOTTOM_RIGHT); + } + // And return new bounds. + return CalculateWindowBounds(position_relative_to); +} + +void InfoBubble::ContentView::GetPreferredSize(CSize* pref) { + DCHECK(GetChildViewCount() == 1); + View* content = GetChildViewAt(0); + content->GetPreferredSize(pref); + pref->cx += kBorderSize + kBorderSize + kInfoBubbleViewLeftMargin + + kInfoBubbleViewRightMargin; + pref->cy += kBorderSize + kBorderSize + kArrowSize + + kInfoBubbleViewTopMargin + kInfoBubbleViewBottomMargin; +} + +void InfoBubble::ContentView::Layout() { + DCHECK(GetChildViewCount() == 1); + View* content = GetChildViewAt(0); + int x = kBorderSize; + int y = kBorderSize; + int width = GetWidth() - kBorderSize - kBorderSize - + kInfoBubbleViewLeftMargin - kInfoBubbleViewRightMargin; + int height = GetHeight() - kBorderSize - kBorderSize - kArrowSize - + kInfoBubbleViewTopMargin - kInfoBubbleViewBottomMargin; + if (IsTop()) + y += kArrowSize; + x += kInfoBubbleViewLeftMargin; + y += kInfoBubbleViewTopMargin; + content->SetBounds(x, y, width, height); +} + +HRGN InfoBubble::ContentView::GetMask(const CSize &size) { + gfx::Path mask; + + // Redefine the window visible region so that our dropshadows look right. + SkScalar width = SkIntToScalar(size.cx); + SkScalar height = SkIntToScalar(size.cy); + SkScalar arrow_size = SkIntToScalar(kArrowSize); + SkScalar arrow_x = SkIntToScalar( + (IsLeft() ? kArrowXOffset : width - kArrowXOffset) - 1); + SkScalar corner_size = SkIntToScalar(kInfoBubbleCornerHeight); + + if (IsTop()) { + // Top left corner. + mask.moveTo(0, arrow_size + corner_size - 1); + mask.lineTo(corner_size - 1, arrow_size); + + // Draw the arrow and the notch of the arrow. + mask.lineTo(arrow_x - arrow_size, arrow_size); + mask.lineTo(arrow_x, 0); + mask.lineTo(arrow_x + 3, 0); + mask.lineTo(arrow_x + arrow_size + 3, arrow_size); + + // Top right corner. + mask.lineTo(width - corner_size + 1, arrow_size); + mask.lineTo(width, arrow_size + corner_size - 1); + + // Bottom right corner. + mask.lineTo(width, height - corner_size); + mask.lineTo(width - corner_size, height); + + // Bottom left corner. + mask.lineTo(corner_size, height); + mask.lineTo(0, height - corner_size); + } else { + // Top left corner. + mask.moveTo(0, corner_size - 1); + mask.lineTo(corner_size - 1, 0); + + // Top right corner. + mask.lineTo(width - corner_size + 1, 0); + mask.lineTo(width, corner_size - 1); + + // Bottom right corner. + mask.lineTo(width, height - corner_size - arrow_size); + mask.lineTo(width - corner_size, height - arrow_size); + + // Draw the arrow and the notch of the arrow. + mask.lineTo(arrow_x + arrow_size + 2, height - arrow_size); + mask.lineTo(arrow_x + 2, height); + mask.lineTo(arrow_x + 1, height); + mask.lineTo(arrow_x - arrow_size + 1, height - arrow_size); + + // Bottom left corner. + mask.lineTo(corner_size, height - arrow_size); + mask.lineTo(0, height - corner_size - arrow_size); + } + + mask.close(); + + return mask.CreateHRGN(); +} + +void InfoBubble::ContentView::Paint(ChromeCanvas* canvas) { + int bubble_x = 0; + int bubble_y = 0; + int bubble_w = GetWidth(); + int bubble_h = GetHeight() - kArrowSize; + + int border_w = bubble_w - 2 * kInfoBubbleCornerWidth; + int border_h = bubble_h - 2 * kInfoBubbleCornerHeight; + + if (IsTop()) + bubble_y += kArrowSize; + + // Fill in the background. + // Left side. + canvas->FillRectInt(kBackgroundColor, + bubble_x, bubble_y + kInfoBubbleCornerHeight, + kInfoBubbleCornerWidth, border_h); + // Center Column. + canvas->FillRectInt(kBackgroundColor, + kInfoBubbleCornerWidth, bubble_y, + border_w, bubble_h); + // Right Column. + canvas->FillRectInt(kBackgroundColor, + bubble_w - kInfoBubbleCornerWidth, + bubble_y + kInfoBubbleCornerHeight, + kInfoBubbleCornerWidth, border_h); + + // Draw the border. + // Top border. + canvas->DrawRectInt(kBorderColor1, + kInfoBubbleCornerWidth, bubble_y, + border_w, + 0); + // Bottom border. + canvas->DrawRectInt(kBorderColor1, + kInfoBubbleCornerWidth, bubble_y + bubble_h - 1, + border_w, 0); + // Left border. + canvas->DrawRectInt(kBorderColor1, + bubble_x, bubble_y + kInfoBubbleCornerHeight, + 0, border_h); + + // Right border. + canvas->DrawRectInt(kBorderColor1, + GetWidth() - 1, bubble_y + kInfoBubbleCornerHeight, + 0, border_h); + + // Draw the corners. + canvas->DrawBitmapInt(*kInfoBubbleCornerTopLeft, 0, bubble_y); + canvas->DrawBitmapInt(*kInfoBubbleCornerTopRight, + bubble_w - kInfoBubbleCornerWidth, bubble_y); + canvas->DrawBitmapInt(*kInfoBubbleCornerBottomLeft, 0, + bubble_y + bubble_h - kInfoBubbleCornerHeight); + canvas->DrawBitmapInt(*kInfoBubbleCornerBottomRight, + bubble_w - kInfoBubbleCornerWidth, + bubble_y + bubble_h - kInfoBubbleCornerHeight); + + // Draw the arrow and the notch of the arrow. + int arrow_x = IsLeft() ? kArrowXOffset : GetWidth() - kArrowXOffset; + int arrow_y = IsTop() ? bubble_y : bubble_y + bubble_h - 1; + const int arrow_delta = IsTop() ? -1 : 1; + for (int i = 0, y = arrow_y; i <= kArrowSize; ++i, y += arrow_delta) { + if (kArrowSize != i) { + // Draw the notch formed by the arrow. + canvas->FillRectInt(kBackgroundColor, arrow_x - (kArrowSize - i) + 1, + y, (kArrowSize - i) * 2 - 1, 1); + } + // Draw the sides of the arrow. + canvas->FillRectInt(kBorderColor1, arrow_x - (kArrowSize - i), y, 1, 1); + canvas->FillRectInt(kBorderColor1, arrow_x + (kArrowSize - i), y, 1, 1); + if (i != 0) { + canvas->FillRectInt(kBorderColor2, arrow_x - (kArrowSize - i) - 1, y, 1, + 1); + canvas->FillRectInt(kBorderColor2, arrow_x + (kArrowSize - i) + 1, y, 1, + 1); + } + } +} + +gfx::Rect InfoBubble::ContentView::CalculateWindowBounds( + const gfx::Rect& position_relative_to) { + CSize pref; + GetPreferredSize(&pref); + int x = position_relative_to.x() + position_relative_to.width() / 2; + int y; + if (IsLeft()) + x -= kArrowXOffset; + else + x = x + kArrowXOffset - pref.cx; + if (IsTop()) { + y = position_relative_to.bottom() + kArrowToContentPadding; + } else { + y = position_relative_to.y() - kArrowToContentPadding - pref.cy; + } + return gfx::Rect(x, y, pref.cx, pref.cy); +} diff --git a/chrome/browser/views/info_bubble.h b/chrome/browser/views/info_bubble.h new file mode 100644 index 0000000..ee4a2b3 --- /dev/null +++ b/chrome/browser/views/info_bubble.h @@ -0,0 +1,193 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INFO_BUBBLE_H__ +#define CHROME_BROWSER_VIEWS_INFO_BUBBLE_H__ + +#include "chrome/common/slide_animation.h" +#include "chrome/views/hwnd_view_container.h" + +// InfoBubble is used to display an arbitrary view above all other windows. +// Think of InfoBubble as a tooltip that allows you to embed an arbitrary view +// in the tooltip. Additionally the InfoBubble renders an arrow pointing at +// the region the info bubble is providing the information about. +// +// To use an InfoBubble invoke Show and it'll take care of the rest. InfoBubble +// (or rather ContentView) insets the content view for you, so that the +// content typically shouldn't have any additional margins around the view. + +class ChromeFrame; +class InfoBubble; + +class InfoBubbleDelegate { + public: + // Called when the InfoBubble is closing and is about to be deleted. + virtual void InfoBubbleClosing(InfoBubble* info_bubble) = 0; + + // Whether the InfoBubble should be closed when the Esc key is pressed. + virtual bool CloseOnEscape() = 0; +}; + +class InfoBubble : public ChromeViews::HWNDViewContainer, + public ChromeViews::AcceleratorTarget, + public AnimationDelegate { + public: + // Shows the InfoBubble. The InfoBubble is parented to parent_hwnd, contains + // the View content and positioned relative to the screen position + // position_relative_to. Show takes ownership of content and deletes the + // create InfoBubble when another window is activated. You can explicitly + // close the bubble by invoking Close. A delegate may optionally be provided + // to be notified when the InfoBubble is closed and to prevent the InfoBubble + // from being closed when the Escape key is pressed (which is the default + // behavior if there is no delegate). + static InfoBubble* Show(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content, + InfoBubbleDelegate* delegate); + + InfoBubble(); + virtual ~InfoBubble(); + + // Creates the InfoBubble. + void Init(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content); + + // Sets the delegate for that InfoBubble. + void SetDelegate(InfoBubbleDelegate* delegate) { delegate_ = delegate; } + + // The InfoBubble is automatically closed when it loses activation status. + virtual void OnActivate(UINT action, BOOL minimized, HWND window); + + // Return our rounded window shape. + virtual void OnSize(UINT param, const CSize& size); + + // Overridden to notify the owning ChromeFrame the bubble is closing. + virtual void Close(); + + // AcceleratorTarget method: + virtual bool AcceleratorPressed(const ChromeViews::Accelerator& accelerator); + + // AnimationDelegate Implementation + virtual void AnimationProgressed(const Animation* animation); + + protected: + + // InfoBubble::CreateContentView() creates one of these. ContentView houses + // the supplied content as its only child view, renders the arrow/border of + // the bubble and sizes the content. + class ContentView : public ChromeViews::View { + public: + // Possible edges the arrow is aligned along. + enum ArrowEdge { + TOP_LEFT = 0, + TOP_RIGHT = 1, + BOTTOM_LEFT = 2, + BOTTOM_RIGHT = 3 + }; + + // Creates the ContentView. The supplied view is added as the only child of + // the ContentView. + ContentView(ChromeViews::View* content, InfoBubble* host); + + virtual ~ContentView() {} + + // Returns the bounds for the window to contain this view. + // + // This invokes the method of the same name that doesn't take an HWND, if + // the returned bounds don't fit on the monitor containing parent_hwnd, + // the arrow edge is adjusted. + virtual gfx::Rect CalculateWindowBounds( + HWND parent_hwnd, + const gfx::Rect& position_relative_to); + + // Sets the edge the arrow is rendered at. + void SetArrowEdge(ArrowEdge arrow_edge) { arrow_edge_ = arrow_edge; } + + // Returns the preferred size, which is the sum of the preferred size of + // the content and the border/arrow. + virtual void GetPreferredSize(CSize* pref); + + // Positions the content relative to the border. + virtual void Layout(); + + virtual void DidChangeBounds(const CRect& previous, const CRect& current) { + Layout(); + } + + // Return the mask for the content view. + HRGN GetMask(const CSize& size); + + // Paints the background and arrow appropriately. + virtual void Paint(ChromeCanvas* canvas); + + // Returns true if the arrow is positioned along the top edge of the + // view. If this returns false the arrow is positioned along the bottom + // edge. + bool IsTop() { return (arrow_edge_ & 2) == 0; } + + // Returns true if the arrow is positioned along the left edge of the + // view. If this returns false the arrow is positioned along the right edge. + bool IsLeft() { return (arrow_edge_ & 1) == 0; } + + private: + + // Returns the bounds for the window containing us based on the current + // arrow edge. + gfx::Rect CalculateWindowBounds(const gfx::Rect& position_relative_to); + + // Edge to draw the arrow at. + ArrowEdge arrow_edge_; + + // The bubble we're in. + InfoBubble* host_; + + DISALLOW_EVIL_CONSTRUCTORS(ContentView); + }; + + // Creates and return a new ContentView containing content. + virtual ContentView* CreateContentView(ChromeViews::View* content); + + // Returns the ChromeFrame that owns this InfoBubble. + ChromeFrame* GetHostingFrame(); + + private: + // The delegate notified when the InfoBubble is closed. + InfoBubbleDelegate* delegate_; + + // The content view contained by the infobubble. + ContentView* content_view_; + + // The fade-in animation. + scoped_ptr<SlideAnimation> fade_animation_; + + DISALLOW_EVIL_CONSTRUCTORS(InfoBubble); +}; + +#endif // CHROME_BROWSER_VIEWS_INFO_BUBBLE_H__ diff --git a/chrome/browser/views/input_window.cc b/chrome/browser/views/input_window.cc new file mode 100644 index 0000000..058e29d --- /dev/null +++ b/chrome/browser/views/input_window.cc @@ -0,0 +1,194 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/input_window.h" + +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/text_field.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +// Width to make the text field, in pixels. +static const int kTextFieldWidth = 200; + +// ContentView ---------------------------------------------------------------- + +// ContentView, as the name implies, is the content view for the InputWindow. +// It registers accelerators that accept/cancel the input. + +class ContentView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::TextField::Controller { + public: + explicit ContentView(InputWindowDelegate* delegate) + : delegate_(delegate), + window_(NULL), + focus_grabber_factory_(this) { + DCHECK(delegate_); + } + + void set_window(ChromeViews::Window* window) { window_ = window; } + + // ChromeViews::DialogDelegate overrides: + virtual bool IsDialogButtonEnabled(DialogButton button) const; + virtual bool Accept(); + virtual bool Cancel(); + virtual void WindowClosing(); + virtual std::wstring GetWindowTitle() const; + virtual bool IsModal() const { return true; } + + // ChromeViews::TextField::Controller overrides: + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField*, UINT, TCHAR, UINT, + UINT) {} + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Set up dialog controls and layout. + void InitControlLayout(); + + // Sets focus to the first focusable element within the dialog. + void FocusFirstFocusableControl(); + + // The TextField that the user can type into. + ChromeViews::TextField* text_field_; + + // The delegate that the ContentView uses to communicate changes to the + // caller. + InputWindowDelegate* delegate_; + + // The Window that owns this view. + ChromeViews::Window* window_; + + // Helps us set focus to the first TextField in the window. + ScopedRunnableMethodFactory<ContentView> focus_grabber_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(ContentView); +}; + +/////////////////////////////////////////////////////////////////////////////// +// ContentView, ChromeViews::DialogDelegate implementation: + +bool ContentView::IsDialogButtonEnabled(DialogButton button) const { + if (button == DIALOGBUTTON_OK && !delegate_->IsValid(text_field_->GetText())) + return false; + return true; +} + +bool ContentView::Accept() { + delegate_->InputAccepted(text_field_->GetText()); + return true; +} + +bool ContentView::Cancel() { + delegate_->InputCanceled(); + return true; +} + +void ContentView::WindowClosing() { + delegate_->WindowClosing(); +} + +std::wstring ContentView::GetWindowTitle() const { + return delegate_->GetWindowTitle(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentView, ChromeViews::TextField::Controller implementation: + +void ContentView::ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents) { + window_->UpdateDialogButtons(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentView, protected: + +void ContentView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (is_add && child == this) + InitControlLayout(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentView, private: + +void ContentView::InitControlLayout() { + text_field_ = new ChromeViews::TextField; + text_field_->SetText(delegate_->GetTextFieldContents()); + text_field_->SetController(this); + + using ChromeViews::ColumnSet; + using ChromeViews::GridLayout; + + // TODO(sky): Vertical alignment should be baseline. + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + ColumnSet* c1 = layout->AddColumnSet(0); + c1->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + c1->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + c1->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, kTextFieldWidth, kTextFieldWidth); + + layout->StartRow(0, 0); + ChromeViews::Label* label = + new ChromeViews::Label(delegate_->GetTextFieldLabel()); + layout->AddView(label); + layout->AddView(text_field_); + + MessageLoop::current()->PostTask(FROM_HERE, + focus_grabber_factory_.NewRunnableMethod( + &ContentView::FocusFirstFocusableControl)); +} + +void ContentView::FocusFirstFocusableControl() { + text_field_->SelectAll(); + text_field_->RequestFocus(); +} + +ChromeViews::Window* CreateInputWindow(HWND parent_hwnd, + InputWindowDelegate* delegate) { + ContentView* cv = new ContentView(delegate); + ChromeViews::Window* window = ChromeViews::Window::CreateChromeWindow( + parent_hwnd, gfx::Rect(), cv, cv); + cv->set_window(window); + window->UpdateDialogButtons(); + return window; +} diff --git a/chrome/browser/views/input_window.h b/chrome/browser/views/input_window.h new file mode 100644 index 0000000..bc9c162 --- /dev/null +++ b/chrome/browser/views/input_window.h @@ -0,0 +1,68 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_INPUT_WINDOW_H__ +#define CHROME_BROWSER_VIEWS_INPUT_WINDOW_H__ + +#include "chrome/views/dialog_delegate.h" + +// InputWindowDelegate -------------------------------------------------------- + +class InputWindowDelegate : public ChromeViews::DialogDelegate { + public: + // Returns the text displayed on the label preceding the text field. + virtual std::wstring GetTextFieldLabel() = 0; + + // Returns the initial contents of the text field. + virtual std::wstring GetTextFieldContents() { + return std::wstring(); + } + + // Returns whether the text is valid. InputAccepted is only invoked if the + // text is valid. + virtual bool IsValid(const std::wstring& text) { + return true; + } + + // Invoked when the user presses the ok button and the text is fvalid. + virtual void InputAccepted(const std::wstring& text) = 0; + + // Invoked when the user cancels the dialog. + virtual void InputCanceled() {} +}; + + +namespace ChromeViews { +class Window; +}; + +ChromeViews::Window* CreateInputWindow(HWND parent_hwnd, + InputWindowDelegate* delegate); + +#endif // CHROME_BROWSER_VIEWS_INPUT_WINDOW_H__ diff --git a/chrome/browser/views/keyword_editor_view.cc b/chrome/browser/views/keyword_editor_view.cc new file mode 100644 index 0000000..04acd38 --- /dev/null +++ b/chrome/browser/views/keyword_editor_view.cc @@ -0,0 +1,694 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/keyword_editor_view.h" + +#include <vector> + +#include "base/gfx/png_decoder.h" +#include "base/string_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/edit_keyword_controller.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/views/background.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/text_field.h" +#include "chrome/views/window.h" +#include "googleurl/src/gurl.h" +#include "skia/include/SkBitmap.h" + +#include "generated_resources.h" + +using ChromeViews::GridLayout; +using ChromeViews::NativeButton; +using ChromeViews::TableColumn; + +// Group IDs used by TemplateURLTableModel. +static const int kMainGroupID = 0; +static const int kOtherGroupID = 1; + +// ModelEntry ---------------------------------------------------- + +// ModelEntry wraps a TemplateURL as returned from the TemplateURL. +// ModelEntry also tracks state information about the URL. + +// Icon used while loading, or if a specific favicon can't be found. +static SkBitmap* default_icon = NULL; + +class ModelEntry { + public: + explicit ModelEntry(TemplateURLTableModel* model, + const TemplateURL& template_url) + : model_(model), + template_url_(template_url) { + load_state_ = NOT_LOADED; + if (!default_icon) { + default_icon = ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_DEFAULT_FAVICON); + } + } + + const TemplateURL& template_url() { + return template_url_; + } + + SkBitmap GetIcon() { + if (load_state_ == NOT_LOADED) + LoadFavIcon(); + if (!fav_icon_.isNull()) + return fav_icon_; + return *default_icon; + } + + // Resets internal status so that the next time the icon is asked for its + // fetched again. This should be invoked if the url is modified. + void ResetIcon() { + load_state_ = NOT_LOADED; + fav_icon_ = SkBitmap(); + } + + private: + // State of the favicon. + enum LoadState { + NOT_LOADED, + LOADING, + LOADED + }; + + void LoadFavIcon() { + load_state_ = LOADED; + HistoryService* hs = + model_->template_url_model()->profile()->GetHistoryService( + Profile::EXPLICIT_ACCESS); + if (!hs) + return; + GURL fav_icon_url = template_url().GetFavIconURL(); + if (!fav_icon_url.is_valid()) { + // The favicon url isn't always set. Guess at one here. + if (template_url_.url() && template_url_.url()->IsValid()) { + GURL url = GURL(template_url_.url()->url()); + if (url.is_valid()) + fav_icon_url = TemplateURL::GenerateFaviconURL(url); + } + if (!fav_icon_url.is_valid()) + return; + } + load_state_ = LOADING; + hs->GetFavIcon(fav_icon_url, + &request_consumer_, + NewCallback(this, &ModelEntry::OnFavIconDataAvailable)); + } + + void OnFavIconDataAvailable( + HistoryService::Handle handle, + bool know_favicon, + scoped_refptr<RefCountedBytes> data, + bool expired, + GURL icon_url) { + load_state_ = LOADED; + if (know_favicon && data.get() && + PNGDecoder::Decode(&data->data, &fav_icon_)) { + model_->FavIconAvailable(this); + } + } + + const TemplateURL& template_url_; + SkBitmap fav_icon_; + LoadState load_state_; + TemplateURLTableModel* model_; + CancelableRequestConsumer request_consumer_; + + DISALLOW_EVIL_CONSTRUCTORS(ModelEntry); +}; + +// TemplateURLTableModel ----------------------------------------- + +TemplateURLTableModel::TemplateURLTableModel( + TemplateURLModel* template_url_model) + : observer_(NULL), + template_url_model_(template_url_model) { + DCHECK(template_url_model); + Reload(); +} + +TemplateURLTableModel::~TemplateURLTableModel() { + STLDeleteElements(&entries_); + entries_.clear(); +} + +void TemplateURLTableModel::Reload() { + STLDeleteElements(&entries_); + entries_.clear(); + + std::vector<const TemplateURL*> urls = template_url_model_->GetTemplateURLs(); + + // Keywords that can be made the default first. + for (std::vector<const TemplateURL*>::iterator i = urls.begin(); + i != urls.end(); ++i) { + const TemplateURL& template_url = *(*i); + // NOTE: we don't use ShowInDefaultList here to avoid items bouncing around + // the lists while editing. + if (template_url.show_in_default_list()) + entries_.push_back(new ModelEntry(this, template_url)); + } + + last_search_engine_index_ = static_cast<int>(entries_.size()); + + // Then the rest. + for (std::vector<const TemplateURL*>::iterator i = urls.begin(); + i != urls.end(); ++i) { + const TemplateURL* template_url = *i; + // NOTE: we don't use ShowInDefaultList here to avoid things bouncing + // the lists while editing. + if (!template_url->show_in_default_list()) + entries_.push_back(new ModelEntry(this, *template_url)); + } + + if (observer_) + observer_->OnModelChanged(); +} + +int TemplateURLTableModel::RowCount() { + return static_cast<int>(entries_.size()); +} + +std::wstring TemplateURLTableModel::GetText(int row, int col_id) { + DCHECK(row >= 0 && row < RowCount()); + const TemplateURL& url = entries_[row]->template_url(); + + switch (col_id) { + case IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN: + return (template_url_model_->GetDefaultSearchProvider() == &url) ? + l10n_util::GetStringF(IDS_SEARCH_ENGINES_EDITOR_DEFAULT_ENGINE, + url.short_name()) : + url.short_name(); + + case IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN: + return url.keyword(); + break; + + default: + NOTREACHED(); + return std::wstring(); + } +} + +SkBitmap TemplateURLTableModel::GetIcon(int row) { + DCHECK(row >= 0 && row < RowCount()); + return entries_[row]->GetIcon(); +} + +void TemplateURLTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +bool TemplateURLTableModel::HasGroups() { + return true; +} + +TemplateURLTableModel::Groups TemplateURLTableModel::GetGroups() { + Groups groups; + + Group search_engine_group; + search_engine_group.title = + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR); + search_engine_group.id = kMainGroupID; + groups.push_back(search_engine_group); + + Group other_group; + other_group.title = + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR); + other_group.id = kOtherGroupID; + groups.push_back(other_group); + + return groups; +} + +int TemplateURLTableModel::GetGroupID(int row) { + DCHECK(row >= 0 && row < RowCount()); + return row < last_search_engine_index_ ? kMainGroupID : kOtherGroupID; +} + +void TemplateURLTableModel::Remove(int index) { + scoped_ptr<ModelEntry> entry(entries_[static_cast<int>(index)]); + entries_.erase(entries_.begin() + static_cast<int>(index)); + if (index < last_search_engine_index_) + last_search_engine_index_--; + if (observer_) + observer_->OnItemsRemoved(index, 1); +} + +void TemplateURLTableModel::Add(int index, const TemplateURL* template_url) { + DCHECK(index >= 0 && index <= RowCount()); + ModelEntry* entry = new ModelEntry(this, *template_url); + entries_.insert(entries_.begin() + index, entry); + if (observer_) + observer_->OnItemsAdded(index, 1); +} + +void TemplateURLTableModel::ReloadIcon(int index) { + DCHECK(index >= 0 && index < RowCount()); + + entries_[index]->ResetIcon(); + + NotifyChanged(index); +} + +const TemplateURL& TemplateURLTableModel::GetTemplateURL(int index) { + return entries_[index]->template_url(); +} + +int TemplateURLTableModel::IndexOfTemplateURL( + const TemplateURL* template_url) { + for (std::vector<ModelEntry*>::iterator i = entries_.begin(); + i != entries_.end(); ++i) { + ModelEntry* entry = *i; + if (&(entry->template_url()) == template_url) + return static_cast<int>(i - entries_.begin()); + } + return -1; +} + +void TemplateURLTableModel::MoveToMainGroup(int index) { + if (index < last_search_engine_index_) + return; // Already in the main group. + + ModelEntry* current_entry = entries_[index]; + entries_.erase(index + entries_.begin()); + if (observer_) + observer_->OnItemsRemoved(index, 1); + + const int new_index = last_search_engine_index_++; + entries_.insert(entries_.begin() + new_index, current_entry); + if (observer_) + observer_->OnItemsAdded(new_index, 1); +} + +void TemplateURLTableModel::NotifyChanged(int index) { + if (observer_) + observer_->OnItemsChanged(index, 1); +} + +void TemplateURLTableModel::FavIconAvailable(ModelEntry* entry) { + std::vector<ModelEntry*>::iterator i = + find(entries_.begin(), entries_.end(), entry); + DCHECK(i != entries_.end()); + NotifyChanged(static_cast<int>(i - entries_.begin())); +} + +// KeywordEditorView ---------------------------------------------------------- + +// If non-null, there is an open editor and this is the window it is contained +// in. +static ChromeViews::Window* open_window = NULL; + +// static +void KeywordEditorView::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kSearchSuggestEnabled, true); +} + +// static +void KeywordEditorView::Show(Profile* profile) { + if (!profile->GetTemplateURLModel()) + return; + + if (open_window != NULL) + open_window->Close(); + DCHECK(!open_window); + + // Both of these will be deleted when the dialog closes. + KeywordEditorView* keyword_editor = new KeywordEditorView(profile); + + // Initialize the UI. By passing in an empty rect KeywordEditorView is + // queried for its preferred size. + open_window = ChromeViews::Window::CreateChromeWindow( + NULL, gfx::Rect(), keyword_editor, keyword_editor); + + open_window->Show(); +} + +KeywordEditorView::KeywordEditorView(Profile* profile) + : profile_(profile), + url_model_(profile->GetTemplateURLModel()) { + DCHECK(url_model_); + Init(); +} + +KeywordEditorView::~KeywordEditorView() { + // Only remove the listener if we installed one. + if (table_model_.get()) { + table_view_->SetModel(NULL); + url_model_->RemoveObserver(this); + } +} + +void KeywordEditorView::AddTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const std::wstring& url) { + DCHECK(!url.empty()); + + UserMetrics::RecordAction(L"KeywordEditor_AddKeyword", profile_); + + TemplateURL* template_url = new TemplateURL(); + template_url->set_short_name(title); + template_url->set_keyword(keyword); + template_url->SetURL(url, 0, 0); + + // There's a bug (1090726) in TableView with groups enabled such that newly + // added items in groups ALWAYS appear at the end, regardless of the index + // passed in. Worse yet, the selected rows get messed up when this happens + // causing other problems. As a work around we always add the item to the end + // of the list. + const int new_index = table_model_->RowCount(); + url_model_->RemoveObserver(this); + table_model_->Add(new_index, template_url); + url_model_->Add(template_url); + url_model_->AddObserver(this); + + table_view_->Select(new_index); +} + +void KeywordEditorView::ModifyTemplateURL(const TemplateURL* template_url, + const std::wstring& title, + const std::wstring& keyword, + const std::wstring& url) { + const int index = table_model_->IndexOfTemplateURL(template_url); + if (index == -1) { + // Will happen if url was deleted out from under us while the user was + // editing it. + return; + } + + // Don't do anything if the entry didn't change. + if (template_url->short_name() == title && + template_url->keyword() == keyword && + ((url.empty() && !template_url->url()) || + (!url.empty() && template_url->url() && + template_url->url()->url() == url))) { + return; + } + + url_model_->RemoveObserver(this); + url_model_->ResetTemplateURL(template_url, title, keyword, url); + if (url_model_->GetDefaultSearchProvider() == template_url && + (!template_url->url() || !template_url->url()->SupportsReplacement())) { + // The entry was the default search provider, but the url has been modified + // so that it no longer supports replacement. Reset the default search + // provider so that it doesn't point to a bogus entry. + url_model_->SetDefaultSearchProvider(NULL); + } + url_model_->AddObserver(this); + table_model_->ReloadIcon(index); // Also calls NotifyChanged(). + + // Force the make default button to update. + OnSelectionChanged(); + + UserMetrics::RecordAction(L"KeywordEditor_ModifiedKeyword", profile_); +} + +void KeywordEditorView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void KeywordEditorView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_SEARCHENGINES_DIALOG_WIDTH_CHARS, + IDS_SEARCHENGINES_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +bool KeywordEditorView::CanResize() const { + return true; +} + +std::wstring KeywordEditorView::GetWindowTitle() const { + return l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE); +} + +int KeywordEditorView::GetDialogButtons() const { + return DIALOGBUTTON_CANCEL; +} + +bool KeywordEditorView::Accept() { + open_window = NULL; + return true; +} + +bool KeywordEditorView::Cancel() { + open_window = NULL; + return true; +} + +void KeywordEditorView::Init() { + DCHECK(!table_model_.get()); + + url_model_->Load(); + url_model_->AddObserver(this); + + table_model_.reset(new TemplateURLTableModel(url_model_)); + + std::vector<TableColumn> columns; + columns.push_back( + TableColumn(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN, + TableColumn::LEFT, -1, .75)); + columns.push_back( + TableColumn(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN, + TableColumn::LEFT, -1, .25)); + table_view_ = new ChromeViews::TableView(table_model_.get(), columns, + ChromeViews::ICON_AND_TEXT, false, true, true); + table_view_->SetObserver(this); + + add_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON)); + add_button_->SetEnabled(url_model_->loaded()); + add_button_->SetListener(this); + + edit_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON)); + edit_button_->SetEnabled(false); + edit_button_->SetListener(this); + + remove_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_REMOVE_BUTTON)); + remove_button_->SetEnabled(false); + remove_button_->SetListener(this); + + make_default_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON)); + make_default_button_->SetEnabled(false); + make_default_button_->SetListener(this); + + enable_suggest_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_SUGGEST_PREF)); + enable_suggest_checkbox_->SetMultiLine(true); + // Enable the Suggest checkbox only if the default provider has Suggest + // capability. + const TemplateURL* default_provider = url_model_->GetDefaultSearchProvider(); + enable_suggest_checkbox_->SetEnabled(default_provider && + (default_provider->suggestions_url() != NULL)); + enable_suggest_checkbox_->SetIsSelected( + profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled)); + enable_suggest_checkbox_->SetListener(this); + + InitLayoutManager(); +} + +void KeywordEditorView::InitLayoutManager() { + const int related_x = kRelatedControlHorizontalSpacing; + const int related_y = kRelatedControlVerticalSpacing; + const int unrelated_y = kUnrelatedControlVerticalSpacing; + + GridLayout* contents_layout = CreatePanelGridLayout(this); + SetLayoutManager(contents_layout); + + // For the table and buttons. + ChromeViews::ColumnSet* column_set = contents_layout->AddColumnSet(0); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, related_x); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + contents_layout->StartRow(0, 0); + contents_layout->AddView(table_view_, 1, 8, GridLayout::FILL, + GridLayout::FILL); + contents_layout->AddView(add_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(edit_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(remove_button_); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->SkipColumns(2); + contents_layout->AddView(make_default_button_); + + contents_layout->AddPaddingRow(1, 0); + + contents_layout->StartRowWithPadding(0, 0, 0, related_y); + contents_layout->AddView(enable_suggest_checkbox_, 3, 1); +} + +void KeywordEditorView::OnSelectionChanged() { + const int selected_row_count = table_view_->SelectedRowCount(); + edit_button_->SetEnabled(selected_row_count == 1); + bool can_make_default = false; + bool can_remove = false; + if (selected_row_count == 1) { + const TemplateURL* selected_url = + &table_model_->GetTemplateURL(table_view_->FirstSelectedRow()); + can_make_default = + (selected_url != url_model_->GetDefaultSearchProvider() && + selected_url->url() && + selected_url->url()->SupportsReplacement()); + can_remove = (selected_url != url_model_->GetDefaultSearchProvider()); + } + remove_button_->SetEnabled(can_remove); + make_default_button_->SetEnabled(can_make_default); +} + +void KeywordEditorView::OnDoubleClick() { + if (edit_button_->IsEnabled()) + ButtonPressed(edit_button_); +} + +void KeywordEditorView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == add_button_) { + EditKeywordController* controller = + new EditKeywordController(GetViewContainer()->GetHWND(), NULL, this, + profile_); + controller->Show(); + } else if (sender == remove_button_) { + DCHECK(table_view_->SelectedRowCount() > 0); + // Remove the observer while we modify the model, that way we don't need to + // worry about the model calling us back when we mutate it. + url_model_->RemoveObserver(this); + int last_row = -1; + for (ChromeViews::TableView::iterator i = table_view_->SelectionBegin(); + i != table_view_->SelectionEnd(); ++i) { + last_row = *i; + const TemplateURL* template_url = &table_model_->GetTemplateURL(last_row); + // Make sure to remove from the table model first, otherwise the + // TemplateURL would be freed. + table_model_->Remove(last_row); + url_model_->Remove(template_url); + } + if (last_row >= table_model_->RowCount()) + last_row = table_model_->RowCount() - 1; + if (last_row >= 0) + table_view_->Select(last_row); + url_model_->AddObserver(this); + + // We may have removed the default provider. Enable the Suggest checkbox + // only if the default provider has Suggest capability. + const TemplateURL* default_provider = + url_model_->GetDefaultSearchProvider(); + enable_suggest_checkbox_->SetEnabled(default_provider && + (default_provider->suggestions_url() != NULL)); + // TODO(pkasting): http://b/1156120 If the template URL model auto-sets a + // new default, ensure that the table model is notified of the change on the + // relevant row. + + UserMetrics::RecordAction(L"KeywordEditor_RemoveKeyword", profile_); + } else if (sender == edit_button_) { + const int selected_row = table_view_->FirstSelectedRow(); + const TemplateURL* template_url = + &table_model_->GetTemplateURL(selected_row); + EditKeywordController* controller = + new EditKeywordController(GetViewContainer()->GetHWND(), template_url, + this, profile_); + controller->Show(); + } else if (sender == enable_suggest_checkbox_) { + profile_->GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, + enable_suggest_checkbox_->IsSelected()); + } else if (sender == make_default_button_) { + MakeDefaultSearchProvider(); + } else { + NOTREACHED(); + } +} + +void KeywordEditorView::OnTemplateURLModelChanged() { + table_model_->Reload(); + add_button_->SetEnabled(url_model_->loaded()); + + // Enable the Suggest checkbox only if the default provider has Suggest + // capability. + const TemplateURL* default_provider = url_model_->GetDefaultSearchProvider(); + enable_suggest_checkbox_->SetEnabled(default_provider && + (default_provider->suggestions_url() != NULL)); +} + +void KeywordEditorView::MakeDefaultSearchProvider() { + int selected_index = table_view_->FirstSelectedRow(); + const TemplateURL* keyword = &table_model_->GetTemplateURL(selected_index); + const TemplateURL* current_default = url_model_->GetDefaultSearchProvider(); + if (current_default == keyword) + return; + + url_model_->RemoveObserver(this); + url_model_->SetDefaultSearchProvider(keyword); + url_model_->AddObserver(this); + + // Enable the Suggest checkbox only if this engine has Suggest capability. + enable_suggest_checkbox_->SetEnabled(keyword->suggestions_url() != NULL); + + // The formatting of the default engine is different; notify the table that + // both old and new entries have changed. + if (current_default != NULL) { + table_model_->NotifyChanged(table_model_->IndexOfTemplateURL( + current_default)); + } + const int new_index = table_model_->IndexOfTemplateURL(keyword); + table_model_->NotifyChanged(new_index); + + // Make sure the new default is in the main group. + table_model_->MoveToMainGroup(selected_index); + + // And select it. + table_view_->Select(new_index); +}
\ No newline at end of file diff --git a/chrome/browser/views/keyword_editor_view.h b/chrome/browser/views/keyword_editor_view.h new file mode 100644 index 0000000..3512648 --- /dev/null +++ b/chrome/browser/views/keyword_editor_view.h @@ -0,0 +1,227 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_KEYWORD_EDITOR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_KEYWORD_EDITOR_VIEW_H__ + +#include <Windows.h> +#include <map> + +#include "base/logging.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/view.h" + +namespace ChromeViews { +class CheckBox; +class Label; +} + +namespace { +class BorderView; +} + +class ModelEntry; +class SkBitmap; +class TemplateURLModel; +class TemplateURLTableModel; + +// TemplateURLTableModel is the TableModel implementation used by +// KeywordEditorView to show the keywords in a TableView. +// +// TemplateURLTableModel has two columns, the first showing the description, +// the second the keyword. +// +// TemplateURLTableModel maintains a vector of ModelEntrys that correspond to +// each row in the tableview. Each ModelEntry wraps a TemplateURL, providing +// the favicon. The entries in the model are sorted such that non-generated +// appear first (grouped together) and are followed by generated keywords. + +class TemplateURLTableModel : public ChromeViews::TableModel { + public: + explicit TemplateURLTableModel(TemplateURLModel* template_url_model); + + virtual ~TemplateURLTableModel(); + + // Reloads the entries from the TemplateURLModel. This should ONLY be invoked + // if the TemplateURLModel wasn't initially loaded and has been loaded. + void Reload(); + + // TableModel overrides. + virtual int RowCount(); + virtual std::wstring GetText(int row, int column); + virtual SkBitmap GetIcon(int row); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + virtual bool HasGroups(); + virtual Groups GetGroups(); + virtual int GetGroupID(int row); + + // Removes the entry at the specified index. This does NOT propagate the + // change to the backend. + void Remove(int index); + + // Adds a new entry at the specified index. This does not propagate the + // change to the backend. + void Add(int index, const TemplateURL* template_url); + + // Reloads the icon at the specified index. + void ReloadIcon(int index); + + // Returns The TemplateURL at the specified index. + const TemplateURL& GetTemplateURL(int index); + + // Returns the index of the TemplateURL, or -1 if it the TemplateURL is not + // found. + int IndexOfTemplateURL(const TemplateURL* template_url); + + // Moves the keyword at the specified index to be at the end of the main + // group. Returns the new index. This does nothing if the entry is already + // in the main group. + void MoveToMainGroup(int index); + + // If there is an observer, it's notified the selected row has changed. + void NotifyChanged(int index); + + TemplateURLModel* template_url_model() const { return template_url_model_; } + + // Returns the index of the last entry shown in the search engines group. + int last_search_engine_index() const { return last_search_engine_index_; } + + private: + friend class ModelEntry; + + // Notification that a model entry has fetched its icon. + void FavIconAvailable(ModelEntry* entry); + + ChromeViews::TableModelObserver* observer_; + + // The entries. + std::vector<ModelEntry*> entries_; + + // The model we're displaying entries from. + TemplateURLModel* template_url_model_; + + // Index of the last search engine in entries_. This is used to determine the + // group boundaries. + int last_search_engine_index_; + + DISALLOW_EVIL_CONSTRUCTORS(TemplateURLTableModel); +}; + +// KeywordEditorView ---------------------------------------------------------- + +// KeywordEditorView allows the user to edit keywords. + +class KeywordEditorView : public ChromeViews::View, + public ChromeViews::TableViewObserver, + public ChromeViews::NativeButton::Listener, + public TemplateURLModelObserver, + public ChromeViews::DialogDelegate { + public: + static void RegisterUserPrefs(PrefService* prefs); + + // Shows the KeywordEditorView for the specified profile. If there is a + // KeywordEditorView already open, it is closed and a new one is shown. + static void Show(Profile* profile); + + explicit KeywordEditorView(Profile* profile); + virtual ~KeywordEditorView(); + + // Invoked when the user succesfully fills out the add keyword dialog. + // Propagates the change to the TemplateURLModel and updates the table model. + void AddTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const std::wstring& url); + + // Invoked when the user modifies a TemplateURL. Update the TemplateURLModel + // and table model appropriatley. + void ModifyTemplateURL(const TemplateURL* template_url, + const std::wstring& title, + const std::wstring& keyword, + const std::wstring& url); + + // Overriden to invoke Layout. + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void GetPreferredSize(CSize* out); + + // DialogDelegate methods: + virtual bool CanResize() const; + virtual std::wstring GetWindowTitle() const; + virtual int GetDialogButtons() const; + virtual bool Accept(); + virtual bool Cancel(); + + // Returns the TemplateURLModel we're using. + TemplateURLModel* template_url_model() const { return url_model_; } + + private: + void Init(); + + // Creates the layout and adds the views to it. + void InitLayoutManager(); + + // TableViewObserver method. Updates buttons contingent on the selection. + virtual void OnSelectionChanged(); + // Edits the selected item. + virtual void OnDoubleClick(); + + // Button::ButtonListener method. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // TemplateURLModelObserver notification. + virtual void OnTemplateURLModelChanged(); + + // Toggles whether the selected keyword is the default search provider. + void MakeDefaultSearchProvider(); + + // The profile. + Profile* profile_; + + // Model containing TemplateURLs. We listen for changes on this and propagate + // them to the table model. + TemplateURLModel* url_model_; + + // Model for the TableView. + scoped_ptr<TemplateURLTableModel> table_model_; + + // All the views are added as children, so that we don't need to delete + // them directly. + ChromeViews::TableView* table_view_; + ChromeViews::NativeButton* add_button_; + ChromeViews::NativeButton* edit_button_; + ChromeViews::NativeButton* remove_button_; + ChromeViews::NativeButton* make_default_button_; + ChromeViews::CheckBox* enable_suggest_checkbox_; + + DISALLOW_EVIL_CONSTRUCTORS(KeywordEditorView); +}; + +#endif // CHROME_BROWSER_VIEWS_KEYWORD_EDITOR_VIEW_H__ diff --git a/chrome/browser/views/location_bar_view.cc b/chrome/browser/views/location_bar_view.cc new file mode 100644 index 0000000..515646d --- /dev/null +++ b/chrome/browser/views/location_bar_view.cc @@ -0,0 +1,1048 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/location_bar_view.h" + +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/alternate_nav_url_fetcher.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/page_info_window.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/ssl_error_info.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/browser/view_ids.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/browser/views/first_run_bubble.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/win_util.h" +#include "chrome/views/background.h" +#include "chrome/views/border.h" +#include "chrome/views/view_container.h" +#include "googleurl/src/gurl.h" +#include "googleurl/src/url_canon.h" +#include "generated_resources.h" + +using ChromeViews::View; + +const int LocationBarView::kTextVertMargin = 2; + +const COLORREF LocationBarView::kBackgroundColorByLevel[] = { + RGB(255, 245, 195), // SecurityLevel SECURE: Yellow. + RGB(255, 255, 255), // SecurityLevel NORMAL: White. + RGB(255, 255, 255), // SecurityLevel INSECURE: White. +}; + +// The margins around the solid color we draw. +static const int kBackgroundVertMargin = 2; +static const int kBackgroundHoriMargin = 0; + +// Padding on the right and left of the entry field. +static const int kEntryPadding = 3; + +// Padding between the entry and the leading/trailing views. +static const int kInnerPadding = 3; + +static const SkBitmap* kBackground = NULL; + +static const SkBitmap* kPopupBackgroundLeft = NULL; +static const SkBitmap* kPopupBackgroundCenter = NULL; +static const SkBitmap* kPopupBackgroundRight = NULL; +static const int kPopupBackgroundVertMargin = 2; +static const int kPopupBackgroundHorzMargin = 2; + +// The delay the mouse has to be hovering over the lock/warning icon before the +// info bubble is shown. +static const int kInfoBubbleHoverDelayMs = 500; + +// The tab key image. +static const SkBitmap* kTabButtonBitmap = NULL; + +// Returns the description for a keyword. +static std::wstring GetKeywordDescription(Profile* profile, + const std::wstring& keyword) { + // Make sure the TemplateURL still exists. + // TODO(sky): Once LocationBarView adds a listener to the TemplateURLModel + // to track changes to the model, this should become a DCHECK. + const TemplateURL* template_url = + profile->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword); + return template_url ? template_url->short_name() : std::wstring(); +} + +LocationBarView::LocationBarView(Profile* profile, + CommandController* controller, + ToolbarModel* model, + Delegate* delegate, + bool popup_window_mode) + : profile_(profile), + controller_(controller), + model_(model), + delegate_(delegate), + disposition_(CURRENT_TAB), + location_entry_view_(NULL), + selected_keyword_view_(profile), + keyword_hint_view_(profile), + type_to_search_view_(l10n_util::GetString(IDS_OMNIBOX_EMPTY_TEXT)), + security_image_view_(profile, model), + popup_window_mode_(popup_window_mode), + first_run_bubble_(this) { + DCHECK(profile_); + SetID(VIEW_ID_LOCATION_BAR); + SetFocusable(true); + + if (!kBackground) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + kBackground = rb.GetBitmapNamed(IDR_LOCATIONBG); + kPopupBackgroundLeft = + rb.GetBitmapNamed(IDR_LOCATIONBG_POPUPMODE_LEFT); + kPopupBackgroundCenter = + rb.GetBitmapNamed(IDR_LOCATIONBG_POPUPMODE_CENTER); + kPopupBackgroundRight = + rb.GetBitmapNamed(IDR_LOCATIONBG_POPUPMODE_RIGHT); + } +} + +bool LocationBarView::IsInitialized() const { + return location_entry_view_ != NULL; +} + +void LocationBarView::Init() { + if (popup_window_mode_) { + font_ = ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont); + } else { + // Use a larger version of the system font. + font_ = font_.DeriveFont(3); + } + + // URL edit field. + ChromeViews::ViewContainer* vc = GetViewContainer(); + DCHECK(vc) << "LocationBarView::Init - vc is NULL!"; + location_entry_.reset(new AutocompleteEdit(font_, this, model_, this, + vc->GetHWND(), + profile_, controller_, + popup_window_mode_)); + + // View container for URL edit field. + location_entry_view_ = new ChromeViews::HWNDView; + DCHECK(location_entry_view_) << "LocationBarView::Init - OOM!"; + location_entry_view_->SetID(VIEW_ID_AUTOCOMPLETE); + AddChildView(location_entry_view_); + location_entry_view_->SetAssociatedFocusView(this); + location_entry_view_->Attach(location_entry_->m_hWnd); + + AddChildView(&selected_keyword_view_); + selected_keyword_view_.SetFont(font_); + selected_keyword_view_.SetVisible(false); + selected_keyword_view_.SetParentOwned(false); + + DWORD sys_color = GetSysColor(COLOR_GRAYTEXT); + SkColor gray = SkColorSetRGB(GetRValue(sys_color), GetGValue(sys_color), + GetBValue(sys_color)); + + AddChildView(&type_to_search_view_); + type_to_search_view_.SetVisible(false); + type_to_search_view_.SetFont(font_); + type_to_search_view_.SetColor(gray); + type_to_search_view_.SetParentOwned(false); + + AddChildView(&keyword_hint_view_); + keyword_hint_view_.SetVisible(false); + keyword_hint_view_.SetFont(font_); + keyword_hint_view_.SetColor(gray); + keyword_hint_view_.SetParentOwned(false); + + AddChildView(&security_image_view_); + security_image_view_.SetVisible(false); + security_image_view_.SetParentOwned(false); + + AddChildView(&info_label_); + info_label_.SetVisible(false); + info_label_.SetParentOwned(false); + + // Notify us when any ancestor is resized. In this case we want to tell the + // AutocompleteEdit to close its popup. + SetNotifyWhenVisibleBoundsInRootChanges(true); + + // Initialize the location entry. We do this to avoid a black flash which is + // visible when the location entry has just been initialized. + Update(NULL); + + OnChanged(); +} + +void LocationBarView::Update(const TabContents* tab_for_state_restoring) { + SetSecurityIcon(model_->GetIcon()); + std::wstring info_text, info_tooltip; + SkColor text_color; + model_->GetInfoText(&info_text, &text_color, &info_tooltip); + SetInfoText(info_text, text_color, info_tooltip); + location_entry_->Update(tab_for_state_restoring); + Layout(); + SchedulePaint(); +} + +void LocationBarView::Focus() { + ::SetFocus(location_entry_->m_hWnd); +} + +void LocationBarView::SetProfile(Profile* profile) { + DCHECK(profile); + if (profile_ != profile) { + profile_ = profile; + location_entry_->SetProfile(profile); + selected_keyword_view_.set_profile(profile); + keyword_hint_view_.set_profile(profile); + security_image_view_.set_profile(profile); + } +} + +void LocationBarView::GetPreferredSize(CSize *out) { + CSize size; + security_image_view_.GetPreferredSize(&size); + out->cx = 0; + + out->cy = std::max( + (popup_window_mode_ ? kPopupBackgroundCenter : kBackground)->height(), + static_cast<int>(size.cy)); +} + +void LocationBarView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void LocationBarView::Layout() { + DoLayout(true); +} + +void LocationBarView::Paint(ChromeCanvas* canvas) { + View::Paint(canvas); + + SkColor bg = SkColorSetRGB( + GetRValue(kBackgroundColorByLevel[model_->GetSchemeSecurityLevel()]), + GetGValue(kBackgroundColorByLevel[model_->GetSchemeSecurityLevel()]), + GetBValue(kBackgroundColorByLevel[model_->GetSchemeSecurityLevel()])); + + if (popup_window_mode_ == false) { + int bh = kBackground->height(); + + canvas->TileImageInt(*kBackground, 0, (GetHeight() - bh) / 2, GetWidth(), + bh); + + canvas->FillRectInt(bg, kBackgroundHoriMargin, kBackgroundVertMargin, + GetWidth() - 2 * kBackgroundHoriMargin, + bh - kBackgroundVertMargin * 2); + } else { + canvas->TileImageInt(*kPopupBackgroundLeft, 0, 0, + kPopupBackgroundLeft->width(), + kPopupBackgroundLeft->height()); + canvas->TileImageInt(*kPopupBackgroundCenter, + kPopupBackgroundLeft->width(), 0, + GetWidth() - + kPopupBackgroundLeft->width() - + kPopupBackgroundRight->width(), + kPopupBackgroundCenter->height()); + canvas->TileImageInt(*kPopupBackgroundRight, + GetWidth() - kPopupBackgroundRight->width(), + 0, kPopupBackgroundRight->width(), + kPopupBackgroundRight->height()); + + canvas->FillRectInt(bg, kPopupBackgroundHorzMargin, + kPopupBackgroundVertMargin, + GetWidth() - kPopupBackgroundHorzMargin * 2, + kPopupBackgroundCenter->height() - + kPopupBackgroundVertMargin * 2); + } +} + +bool LocationBarView::CanProcessTabKeyEvents() { + // We want to receive tab key events when the hint is showing. + return keyword_hint_view_.IsVisible(); +} + +void LocationBarView::VisibleBoundsInRootChanged() { + location_entry_->ClosePopup(); +} + +bool LocationBarView::OnMousePressed(const ChromeViews::MouseEvent& event) { + UINT msg; + if (event.IsLeftMouseButton()) { + msg = (event.GetFlags() & ChromeViews::MouseEvent::EF_IS_DOUBLE_CLICK) ? + WM_LBUTTONDBLCLK : WM_LBUTTONDOWN; + } else if (event.IsMiddleMouseButton()) { + msg = (event.GetFlags() & ChromeViews::MouseEvent::EF_IS_DOUBLE_CLICK) ? + WM_MBUTTONDBLCLK : WM_MBUTTONDOWN; + } else if (event.IsRightMouseButton()) { + msg = (event.GetFlags() & ChromeViews::MouseEvent::EF_IS_DOUBLE_CLICK) ? + WM_RBUTTONDBLCLK : WM_RBUTTONDOWN; + } else { + NOTREACHED(); + return false; + } + OnMouseEvent(event, msg); + return true; +} + +bool LocationBarView::OnMouseDragged(const ChromeViews::MouseEvent& event) { + OnMouseEvent(event, WM_MOUSEMOVE); + return true; +} + +void LocationBarView::OnMouseReleased(const ChromeViews::MouseEvent& event, + bool canceled) { + UINT msg; + if (canceled) { + msg = WM_CAPTURECHANGED; + } else if (event.IsLeftMouseButton()) { + msg = WM_LBUTTONUP; + } else if (event.IsMiddleMouseButton()) { + msg = WM_MBUTTONUP; + } else if (event.IsRightMouseButton()) { + msg = WM_RBUTTONUP; + } else { + NOTREACHED(); + return; + } + OnMouseEvent(event, msg); +} + +void LocationBarView::OnAutocompleteAccept( + const std::wstring& url, + WindowOpenDisposition disposition, + PageTransition::Type transition, + const std::wstring& alternate_nav_url) { + if (url.empty()) + return; + + location_input_ = url; + disposition_ = disposition; + transition_ = transition; + + if (controller_) { + if (alternate_nav_url.empty()) { + controller_->ExecuteCommand(IDC_OPENURL); + return; + } + + scoped_ptr<AlternateNavURLFetcher> fetcher( + new AlternateNavURLFetcher(alternate_nav_url)); + // The AlternateNavURLFetcher will listen for the next navigation state + // update notification (expecting it to be a new page load) and hook + // itself in to that loading process. + controller_->ExecuteCommand(IDC_OPENURL); + if (fetcher->state() == AlternateNavURLFetcher::NOT_STARTED) { + // I'm not sure this should be reachable, but I'm not also sure enough + // that it shouldn't to stick in a NOTREACHED(). In any case, this is + // harmless; we can simply let the fetcher get deleted here and it will + // clean itself up properly. + } else { + fetcher.release(); // The navigation controller will delete the fetcher. + } + } +} + +void LocationBarView::OnChanged() { + DoLayout(false); +} + +SkBitmap LocationBarView::GetFavIcon() const { + DCHECK(delegate_); + DCHECK(delegate_->GetTabContents()); + return delegate_->GetTabContents()->GetFavIcon(); +} + +std::wstring LocationBarView::GetTitle() const { + DCHECK(delegate_); + DCHECK(delegate_->GetTabContents()); + return delegate_->GetTabContents()->GetTitle(); +} + +void LocationBarView::DoLayout(const bool force_layout) { + if (!location_entry_.get()) + return; + + RECT formatting_rect; + location_entry_->GetRect(&formatting_rect); + RECT edit_bounds; + location_entry_->GetClientRect(&edit_bounds); + + int entry_width = GetWidth() - kEntryPadding - kEntryPadding; + CSize security_image_size; + if (security_image_view_.IsVisible()) { + security_image_view_.GetPreferredSize(&security_image_size); + entry_width -= security_image_size.cx; + } + CSize info_label_size; + if (info_label_.IsVisible()) { + info_label_.GetPreferredSize(&info_label_size); + entry_width -= (info_label_size.cx + kInnerPadding); + } + + const int max_edit_width = entry_width - formatting_rect.left - + (edit_bounds.right - formatting_rect.right); + if (max_edit_width < 0) + return; + const int text_width = TextDisplayWidth(); + bool needs_layout = force_layout; + needs_layout |= AdjustHints(text_width, max_edit_width); + + if (!needs_layout) + return; + + // TODO(sky): baseline layout. + int bh = kBackground->height(); + int location_y = ((GetHeight() - bh) / 2) + kTextVertMargin; + int location_height = bh - (2 * kTextVertMargin); + if (info_label_.IsVisible()) { + info_label_.SetBounds(GetWidth() - kEntryPadding - info_label_size.cx, + location_y, + info_label_size.cx, location_height); + } + if (security_image_view_.IsVisible()) { + const int info_label_width = info_label_size.cx ? + info_label_size.cx + kInnerPadding : 0; + security_image_view_.SetBounds(GetWidth() - kEntryPadding - + info_label_width - + security_image_size.cx, + location_y, + security_image_size.cx, location_height); + } + gfx::Rect location_bounds(kEntryPadding, location_y, entry_width, + location_height); + if (selected_keyword_view_.IsVisible()) { + LayoutView(true, &selected_keyword_view_, text_width, max_edit_width, + &location_bounds); + } else if (keyword_hint_view_.IsVisible()) { + LayoutView(false, &keyword_hint_view_, text_width, max_edit_width, + &location_bounds); + } else if (type_to_search_view_.IsVisible()) { + LayoutView(false, &type_to_search_view_, text_width, max_edit_width, + &location_bounds); + } + + location_entry_view_->SetBounds(location_bounds.x(), + location_bounds.y(), + location_bounds.width(), + location_bounds.height()); + if (!force_layout) { + // If force_layout is false and we got this far it means one of the views + // was added/removed or changed in size. We need to paint ourselves. + SchedulePaint(); + } +} + +int LocationBarView::TextDisplayWidth() { + POINT last_char_position = + location_entry_->PosFromChar(location_entry_->GetTextLength()); + POINT scroll_position; + location_entry_->GetScrollPos(&scroll_position); + const int position_x = last_char_position.x + scroll_position.x; + return UILayoutIsRightToLeft() ? GetWidth() - position_x : position_x; +} + +bool LocationBarView::UsePref(int pref_width, int text_width, int max_width) { + return (pref_width + kInnerPadding + text_width <= max_width); +} + +bool LocationBarView::NeedsResize(View* view, int text_width, int max_width) { + CSize size; + view->GetPreferredSize(&size); + if (!UsePref(size.cx, text_width, max_width)) + view->GetMinimumSize(&size); + return (view->GetWidth() != size.cx); +} + +bool LocationBarView::AdjustHints(int text_width, int max_width) { + const std::wstring keyword(location_entry_->keyword()); + const bool is_keyword_hint(location_entry_->is_keyword_hint()); + const bool show_selected_keyword = !keyword.empty() && !is_keyword_hint; + const bool show_keyword_hint = !keyword.empty() && is_keyword_hint; + bool show_search_hint(location_entry_->show_search_hint()); + DCHECK(keyword.empty() || !show_search_hint); + + if (show_search_hint) { + // Only show type to search if all the text fits. + CSize view_pref; + type_to_search_view_.GetPreferredSize(&view_pref); + show_search_hint = UsePref(view_pref.cx, text_width, max_width); + } + + // NOTE: This isn't just one big || statement as ToggleVisibility MUST be + // invoked for each view. + bool needs_layout = false; + needs_layout |= ToggleVisibility(show_selected_keyword, + &selected_keyword_view_); + needs_layout |= ToggleVisibility(show_keyword_hint, &keyword_hint_view_); + needs_layout |= ToggleVisibility(show_search_hint, &type_to_search_view_); + if (show_selected_keyword) { + if (selected_keyword_view_.keyword() != keyword) { + needs_layout = true; + selected_keyword_view_.SetKeyword(keyword); + } + needs_layout |= NeedsResize(&selected_keyword_view_, text_width, max_width); + } else if (show_keyword_hint) { + if (keyword_hint_view_.keyword() != keyword) { + needs_layout = true; + keyword_hint_view_.SetKeyword(keyword); + } + needs_layout |= NeedsResize(&keyword_hint_view_, text_width, max_width); + } + + return needs_layout; +} + +void LocationBarView::LayoutView(bool leading, ChromeViews::View* view, + int text_width, int max_width, + gfx::Rect* bounds) { + DCHECK(view && bounds); + CSize view_size(0, 0); + view->GetPreferredSize(&view_size); + if (!UsePref(view_size.cx, text_width, max_width)) + view->GetMinimumSize(&view_size); + if (view_size.cx + kInnerPadding < bounds->width()) { + view->SetVisible(true); + if (leading) { + view->SetBounds(bounds->x(), bounds->y(), view_size.cx, bounds->height()); + bounds->Offset(view_size.cx + kInnerPadding, 0); + } else { + view->SetBounds(bounds->right() - view_size.cx, bounds->y(), + view_size.cx, bounds->height()); + } + bounds->set_width(bounds->width() - view_size.cx - kInnerPadding); + } else { + view->SetVisible(false); + } +} + +void LocationBarView::SetSecurityIcon(ToolbarModel::Icon icon) { + switch (icon) { + case ToolbarModel::LOCK_ICON: + security_image_view_.SetImageShown(SecurityImageView::LOCK); + security_image_view_.SetVisible(true); + break; + case ToolbarModel::WARNING_ICON: + security_image_view_.SetImageShown(SecurityImageView::WARNING); + security_image_view_.SetVisible(true); + break; + case ToolbarModel::NO_ICON: + security_image_view_.SetVisible(false); + break; + default: + NOTREACHED(); + security_image_view_.SetVisible(false); + break; + } +} + +void LocationBarView::SetInfoText(const std::wstring& text, + SkColor text_color, + const std::wstring& tooltip_text) { + info_label_.SetVisible(!text.empty()); + info_label_.SetText(text); + info_label_.SetColor(text_color); + info_label_.SetTooltipText(tooltip_text); +} + +bool LocationBarView::ToggleVisibility(bool new_vis, View* view) { + DCHECK(view); + if (view->IsVisible() != new_vis) { + view->SetVisible(new_vis); + return true; + } + return false; +} + +void LocationBarView::OnMouseEvent(const ChromeViews::MouseEvent& event, + UINT msg) { + UINT flags = 0; + if (event.IsControlDown()) + flags |= MK_CONTROL; + if (event.IsShiftDown()) + flags |= MK_SHIFT; + if (event.IsLeftMouseButton()) + flags |= MK_LBUTTON; + if (event.IsMiddleMouseButton()) + flags |= MK_MBUTTON; + if (event.IsRightMouseButton()) + flags |= MK_RBUTTON; + + CPoint screen_point(event.GetLocation()); + ConvertPointToScreen(this, &screen_point); + + location_entry_->HandleExternalMsg(msg, flags, screen_point); +} + +bool LocationBarView::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_GROUPING; + return true; +} + +// SelectedKeywordView ------------------------------------------------------- + +// The background is drawn using ImagePainter3. This is the left/center/right +// image names. +static const int kBorderImages[] = { + IDR_LOCATION_BAR_SELECTED_KEYWORD_BACKGROUND_L, + IDR_LOCATION_BAR_SELECTED_KEYWORD_BACKGROUND_C, + IDR_LOCATION_BAR_SELECTED_KEYWORD_BACKGROUND_R }; + +// Insets around the label. +static const int kTopInset = 0; +static const int kBottomInset = 0; +static const int kLeftInset = 4; +static const int kRightInset = 4; + +// Offset from the top the background is drawn at. +static const int kBackgroundYOffset = 2; + +LocationBarView::SelectedKeywordView::SelectedKeywordView(Profile* profile) + : background_painter_(kBorderImages), + profile_(profile) { + AddChildView(&full_label_); + AddChildView(&partial_label_); + // Full_label and partial_label are deleted by us, make sure View doesn't + // delete them too. + full_label_.SetParentOwned(false); + partial_label_.SetParentOwned(false); + full_label_.SetVisible(false); + partial_label_.SetVisible(false); + full_label_.SetBorder( + ChromeViews::Border::CreateEmptyBorder(kTopInset, kLeftInset, + kBottomInset, kRightInset)); + partial_label_.SetBorder( + ChromeViews::Border::CreateEmptyBorder(kTopInset, kLeftInset, + kBottomInset, kRightInset)); +} + +LocationBarView::SelectedKeywordView::~SelectedKeywordView() { +} + +void LocationBarView::SelectedKeywordView::SetFont(const ChromeFont& font) { + full_label_.SetFont(font); + partial_label_.SetFont(font); +} + +void LocationBarView::SelectedKeywordView::Paint(ChromeCanvas* canvas) { + canvas->TranslateInt(0, kBackgroundYOffset); + background_painter_.Paint(GetWidth(), GetHeight() - kTopInset, canvas); + canvas->TranslateInt(0, -kBackgroundYOffset); +} + +void LocationBarView::SelectedKeywordView::GetPreferredSize(CSize* size) { + full_label_.GetPreferredSize(size); +} + +void LocationBarView::SelectedKeywordView::GetMinimumSize(CSize* size) { + partial_label_.GetMinimumSize(size); +} + +void LocationBarView::SelectedKeywordView::DidChangeBounds( + const CRect& previous, + const CRect& current) { + Layout(); +} + +void LocationBarView::SelectedKeywordView::Layout() { + CSize pref; + GetPreferredSize(&pref); + bool at_pref = (GetWidth() == pref.cx); + if (at_pref) + full_label_.SetBounds(0, 0, GetWidth(), GetHeight()); + else + partial_label_.SetBounds(0, 0, GetWidth(), GetHeight()); + full_label_.SetVisible(at_pref); + partial_label_.SetVisible(!at_pref); +} + +void LocationBarView::SelectedKeywordView::SetKeyword( + const std::wstring& keyword) { + keyword_ = keyword; + if (keyword.empty()) + return; + DCHECK(profile_); + if (!profile_->GetTemplateURLModel()) + return; + + const std::wstring description = GetKeywordDescription(profile_, keyword); + full_label_.SetText(l10n_util::GetStringF(IDS_OMNIBOX_KEYWORD_TEXT, + description)); + const std::wstring min_string = CalculateMinString(description); + if (!min_string.empty()) { + partial_label_.SetText( + l10n_util::GetStringF(IDS_OMNIBOX_KEYWORD_TEXT, min_string)); + } else { + partial_label_.SetText(full_label_.GetText()); + } +} + +std::wstring LocationBarView::SelectedKeywordView::CalculateMinString( + const std::wstring& description) { + // Chop at the first '.' or whitespace. + const size_t dot_index = description.find(L'.'); + const size_t ws_index = description.find_first_of(kWhitespaceWide); + size_t chop_index = std::min(dot_index, ws_index); + if (chop_index == std::wstring::npos) { + // No dot or whitespace, truncate to at most 3 chars. + return l10n_util::TruncateString(description, 3); + } + return description.substr(0, chop_index); +} + +// KeywordHintView ------------------------------------------------------------- + +// Amount of space to offset the tab image from the top of the view by. +static const int kTabImageYOffset = 4; + +LocationBarView::KeywordHintView::KeywordHintView(Profile* profile) + : profile_(profile) { + AddChildView(&leading_label_); + AddChildView(&trailing_label_); + + if (!kTabButtonBitmap) { + kTabButtonBitmap = ResourceBundle::GetSharedInstance(). + GetBitmapNamed(IDR_LOCATION_BAR_KEYWORD_HINT_TAB); + } +} + +LocationBarView::KeywordHintView::~KeywordHintView() { + // Labels are freed by us. Remove them so that View doesn't + // try to free them too. + RemoveChildView(&leading_label_); + RemoveChildView(&trailing_label_); +} + +void LocationBarView::KeywordHintView::SetFont(const ChromeFont& font) { + leading_label_.SetFont(font); + trailing_label_.SetFont(font); +} + +void LocationBarView::KeywordHintView::SetColor(const SkColor& color) { + leading_label_.SetColor(color); + trailing_label_.SetColor(color); +} + +void LocationBarView::KeywordHintView::SetKeyword(const std::wstring& keyword) { + keyword_ = keyword; + if (keyword_.empty()) + return; + DCHECK(profile_); + if (!profile_->GetTemplateURLModel()) + return; + + std::vector<size_t> content_param_offsets; + const std::wstring keyword_hint(l10n_util::GetStringF( + IDS_OMNIBOX_KEYWORD_HINT, std::wstring(), + GetKeywordDescription(profile_, keyword), &content_param_offsets)); + if (content_param_offsets.size() == 2) { + leading_label_.SetText(keyword_hint.substr(0, + content_param_offsets.front())); + trailing_label_.SetText(keyword_hint.substr(content_param_offsets.front())); + } else { + // See comments on an identical NOTREACHED() in search_provider.cc. + NOTREACHED(); + } +} + +void LocationBarView::KeywordHintView::Paint(ChromeCanvas* canvas) { + int image_x = leading_label_.IsVisible() ? leading_label_.GetWidth() : 0; + + // Since we paint the button image directly on the canvas (instead of using a + // child view), we must mirror the button's position manually if the locale + // is right-to-left. + gfx::Rect tab_button_bounds(image_x, + kTabImageYOffset, + kTabButtonBitmap->width(), + kTabButtonBitmap->height()); + tab_button_bounds.set_x(MirroredLeftPointForRect(tab_button_bounds)); + canvas->DrawBitmapInt(*kTabButtonBitmap, + tab_button_bounds.x(), + tab_button_bounds.y()); +} + +void LocationBarView::KeywordHintView::GetPreferredSize(CSize *out) { + // TODO(sky): currently height doesn't matter, once baseline support is + // added this should check baselines. + leading_label_.GetPreferredSize(out); + int width = out->cx; + width += kTabButtonBitmap->width(); + trailing_label_.GetPreferredSize(out); + width += out->cx; + out->cx = width; +} + +void LocationBarView::KeywordHintView::GetMinimumSize(CSize* out) { + // TODO(sky): currently height doesn't matter, once baseline support is + // added this should check baselines. + out->cx = kTabButtonBitmap->width(); +} + +void LocationBarView::KeywordHintView::Layout() { + // TODO(sky): baseline layout. + bool show_labels = (GetWidth() != kTabButtonBitmap->width()); + + leading_label_.SetVisible(show_labels); + trailing_label_.SetVisible(show_labels); + int height = GetHeight(); + int x = 0; + CSize pref; + + if (show_labels) { + leading_label_.GetPreferredSize(&pref); + leading_label_.SetBounds(x, 0, pref.cx, height); + + x += pref.cx + kTabButtonBitmap->width(); + trailing_label_.GetPreferredSize(&pref); + trailing_label_.SetBounds(x, 0, pref.cx, height); + } +} + +void LocationBarView::KeywordHintView::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +// We don't translate accelerators for ALT + numpad digit, they are used for +// entering special characters. +bool LocationBarView::ShouldLookupAccelerators(const ChromeViews::KeyEvent& e) { + if (!e.IsAltDown()) + return true; + + return !win_util::IsNumPadDigit(e.GetCharacter(), e.IsExtendedKey()); +} + +// ShowInfoBubbleTask----------------------------------------------------------- + +class LocationBarView::ShowInfoBubbleTask : public Task { + public: + explicit ShowInfoBubbleTask(LocationBarView::SecurityImageView* image_view); + virtual void Run(); + void Cancel(); + + private: + LocationBarView::SecurityImageView* image_view_; + bool cancelled_; + + DISALLOW_EVIL_CONSTRUCTORS(ShowInfoBubbleTask); +}; + +LocationBarView::ShowInfoBubbleTask::ShowInfoBubbleTask( + LocationBarView::SecurityImageView* image_view) + : cancelled_(false), + image_view_(image_view) { +} + +void LocationBarView::ShowInfoBubbleTask::Run() { + if (cancelled_) + return; + + if (!image_view_->GetViewContainer()->IsActive()) { + // The browser is no longer active. Let's not show the info bubble, this + // would make the browser the active window again. Also makes sure we NULL + // show_info_bubble_task_ to prevent the SecurityImageView from keeping a + // dangling pointer. + image_view_->show_info_bubble_task_ = NULL; + return; + } + + image_view_->ShowInfoBubble(); +} + +void LocationBarView::ShowInfoBubbleTask::Cancel() { + cancelled_ = true; +} + +// ----------------------------------------------------------------------------- + +void LocationBarView::ShowFirstRunBubbleInternal() { + if (!location_entry_view_) + return; + if (!location_entry_view_->GetViewContainer()->IsActive()) { + // The browser is no longer active. Let's not show the info bubble, this + // would make the browser the active window again. + return; + } + + CPoint location(0, 0); + + // If the UI layout is RTL, the coordinate system is not transformed and + // therefore we need to adjust the X coordinate so that bubble appears on the + // right hand side of the location bar. + if (UILayoutIsRightToLeft()) + location.x += GetWidth(); + ChromeViews::View::ConvertPointToScreen(this, &location); + + // We try to guess that 20 pixels offset is a good place for the first + // letter in the OmniBox. + gfx::Rect bounds(location.x, location.y, 20, GetHeight()); + + // Moving the bounds "backwards" so that it appears within the location bar + // if the UI layout is RTL. + if (UILayoutIsRightToLeft()) + bounds.set_x(location.x - 20); + + FirstRunBubble::Show( + location_entry_view_->GetRootView()->GetViewContainer()->GetHWND(), + bounds); +} + +void LocationBarView::ShowFirstRunBubble() { + // We wait 30 milliseconds to open. It allows less flicker. + Task* task = first_run_bubble_.NewRunnableMethod( + &LocationBarView::ShowFirstRunBubbleInternal); + MessageLoop::current()->PostDelayedTask(FROM_HERE, task, 30); +} + +// SecurityImageView------------------------------------------------------------ + +// static +SkBitmap* LocationBarView::SecurityImageView::lock_icon_ = NULL; +SkBitmap* LocationBarView::SecurityImageView::warning_icon_ = NULL; + +LocationBarView::SecurityImageView::SecurityImageView(Profile* profile, + ToolbarModel* model) + : profile_(profile), + model_(model), + show_info_bubble_task_(NULL), + info_bubble_(NULL) { + if (!lock_icon_) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + lock_icon_ = rb.GetBitmapNamed(IDR_LOCK); + warning_icon_ = rb.GetBitmapNamed(IDR_WARNING); + } + SetImageShown(LOCK); +} + +LocationBarView::SecurityImageView::~SecurityImageView() { + if (show_info_bubble_task_) + show_info_bubble_task_->Cancel(); + + if (info_bubble_) { + // We are going to be invalid, make sure the InfoBubble does not keep a + // pointer to us. + info_bubble_->SetDelegate(NULL); + } +} + +void LocationBarView::SecurityImageView::SetImageShown(Image image) { + switch (image) { + case LOCK: + ImageView::SetImage(lock_icon_); + break; + case WARNING: + ImageView::SetImage(warning_icon_); + break; + default: + NOTREACHED(); + break; + } +} + +void LocationBarView::SecurityImageView::ShowInfoBubble() { + std::wstring text; + SkColor text_color; + model_->GetIconHoverText(&text, &text_color); + + CPoint location(0, 0); + ChromeViews::View::ConvertPointToScreen(this, &location); + gfx::Rect bounds(location.x, location.y, GetWidth(), GetHeight()); + + ChromeViews::Label* label = new ChromeViews::Label(text); + label->SetMultiLine(true); + label->SetColor(text_color); + label->SetFont(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont).DeriveFont(2)); + label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + label->SizeToFit(0); + DCHECK(info_bubble_ == NULL); + info_bubble_ = InfoBubble::Show(GetRootView()->GetViewContainer()->GetHWND(), + bounds, label, this); + show_info_bubble_task_ = NULL; +} + +void LocationBarView::SecurityImageView::OnMouseMoved( + const ChromeViews::MouseEvent& event) { + if (show_info_bubble_task_) { + show_info_bubble_task_->Cancel(); + show_info_bubble_task_ = NULL; + } + + if (info_bubble_) { + // If an info bubble is currently showing, nothing to do. + return; + } + + show_info_bubble_task_ = new ShowInfoBubbleTask(this); + MessageLoop::current()->PostDelayedTask(FROM_HERE, show_info_bubble_task_, + kInfoBubbleHoverDelayMs); +} + +void LocationBarView::SecurityImageView::OnMouseExited( + const ChromeViews::MouseEvent& event) { + if (show_info_bubble_task_) { + show_info_bubble_task_->Cancel(); + show_info_bubble_task_ = NULL; + } + + if (info_bubble_) + info_bubble_->Close(); +} + +bool LocationBarView::SecurityImageView::OnMousePressed( + const ChromeViews::MouseEvent& event) { + NavigationEntry* nav_entry = + BrowserList::GetLastActive()->GetSelectedTabContents()-> + controller()->GetActiveEntry(); + PageInfoWindow::Create(profile_, + nav_entry, + GetRootView()->GetViewContainer()->GetHWND(), + PageInfoWindow::SECURITY); + return true; +} + +void LocationBarView::SecurityImageView::InfoBubbleClosing( + InfoBubble* info_bubble) { + info_bubble_ = NULL; +} + +bool LocationBarView::OverrideAccelerator( + const ChromeViews::Accelerator& accelerator) { + return location_entry_->OverrideAccelerator(accelerator); +} diff --git a/chrome/browser/views/location_bar_view.h b/chrome/browser/views/location_bar_view.h new file mode 100644 index 0000000..84bfe32 --- /dev/null +++ b/chrome/browser/views/location_bar_view.h @@ -0,0 +1,424 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__ + +#include <string> + +#include "base/gfx/rect.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/controller.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/toolbar_model.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/image_view.h" +#include "chrome/views/label.h" +#include "chrome/views/painter.h" + +class GURL; +class Profile; + +///////////////////////////////////////////////////////////////////////////// +// +// LocationBarView class +// +// The LocationBarView class is a View subclass that paints the background +// of the URL bar strip and contains its content. +// +///////////////////////////////////////////////////////////////////////////// +class LocationBarView : public ChromeViews::View, + public AutocompleteEdit::Controller { + public: + + class Delegate { + public: + // Should return the current tab contents. + virtual TabContents* GetTabContents() = 0; + + // Called by the location bar view when the user starts typing in the edit. + // This forces our security style to be UNKNOWN for the duration of the + // editing. + virtual void OnInputInProgress(bool in_progress) = 0; + }; + + LocationBarView(Profile* profile, + CommandController* controller, + ToolbarModel* model_, + Delegate* delegate, + bool popup_window_mode); + virtual ~LocationBarView() { } + + void Init(); + + // Returns whether this instance has been initialized by callin Init. Init can + // only be called when the receiving instance is attached to a view container. + bool IsInitialized() const; + + // Updates the location bar. We also reset the bar's permanent text and + // security style, and, if |tab_for_state_restoring| is non-NULL, also restore + // saved state that the tab holds. + void Update(const TabContents* tab_for_state_restoring); + + void SetProfile(Profile* profile); + Profile* profile() { return profile_; } + + // Sizing functions + virtual void GetPreferredSize(CSize *out); + + // Layout and Painting functions + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void Layout(); + virtual void Paint(ChromeCanvas* canvas); + + // No focus border for the location bar, the caret is enough. + virtual void PaintFocusBorder(ChromeCanvas* canvas) { } + + // Overridden from View so we can use <tab> to go into keyword search mode. + virtual bool CanProcessTabKeyEvents(); + + // Called when any ancestor changes its size, asks the AutocompleteEdit to + // close its popup. + virtual void VisibleBoundsInRootChanged(); + + // Event Handlers + virtual bool OnMousePressed(const ChromeViews::MouseEvent& event); + virtual bool OnMouseDragged(const ChromeViews::MouseEvent& event); + virtual void OnMouseReleased(const ChromeViews::MouseEvent& event, + bool canceled); + + // AutocompleteEdit::Controller + virtual void OnAutocompleteAccept(const std::wstring& url, + WindowOpenDisposition disposition, + PageTransition::Type transition, + const std::wstring& alternate_nav_url); + virtual void OnChanged(); + virtual void OnInputInProgress(bool in_progress) { + delegate_->OnInputInProgress(in_progress); + } + virtual SkBitmap GetFavIcon() const; + virtual std::wstring GetTitle() const; + + // Returns the MSAA role + bool GetAccessibleRole(VARIANT* role); + + AutocompleteEdit* location_entry() { + return location_entry_.get(); + } + + std::wstring location_input() { + return location_input_; + } + + WindowOpenDisposition disposition() { + return disposition_; + } + + PageTransition::Type transition() { + return transition_; + } + + // Shows a info bubble that tells the user what the omnibox is and allows + // to change the search providers. + void ShowFirstRunBubble(); + + // Overridden from View. + virtual bool OverrideAccelerator(const ChromeViews::Accelerator& accelerator) ; + + static const int kTextVertMargin; + static const COLORREF kBackgroundColorByLevel[]; + + protected: + void Focus(); + + // Overridden from Chrome::View. + virtual bool ShouldLookupAccelerators(const ChromeViews::KeyEvent& e); + + private: + // View used when the user has selected a keyword. + // + // SelectedKeywordView maintains two labels. One label contains the + // complete description of the keyword, the second contains a truncated + // version of the description. The second is used if there is not enough room + // to display the complete description. + class SelectedKeywordView : public ChromeViews::View { + public: + explicit SelectedKeywordView(Profile* profile); + virtual ~SelectedKeywordView(); + + void SetFont(const ChromeFont& font); + + virtual void Paint(ChromeCanvas* canvas); + + virtual void GetPreferredSize(CSize* out); + virtual void GetMinimumSize(CSize* out); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void Layout(); + + // The current keyword, or an empty string if no keyword is displayed. + void SetKeyword(const std::wstring& keyword); + std::wstring keyword() const { return keyword_; } + + void set_profile(Profile* profile) { profile_ = profile; } + + private: + // Returns the truncated version of description to use. + std::wstring CalculateMinString(const std::wstring& description); + + // The keyword we're showing. If empty, no keyword is selected. + // NOTE: we don't cache the TemplateURL as it is possible for it to get + // deleted out from under us. + std::wstring keyword_; + + // For painting the background. + ChromeViews::HorizontalPainter background_painter_; + + // Label containing the complete description. + ChromeViews::Label full_label_; + + // Label containing the partial description. + ChromeViews::Label partial_label_; + + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(SelectedKeywordView); + }; + + // KeywordHintView is used to display a hint to the user when the selected + // url has a corresponding keyword. + // + // Internally KeywordHintView uses two labels to render the text, and draws + // the tab image itself. + // + // NOTE: This should really be called LocationBarKeywordHintView, but I + // couldn't bring myself to use such a long name. + class KeywordHintView : public ChromeViews::View { + public: + explicit KeywordHintView(Profile* profile); + virtual ~KeywordHintView(); + + void SetFont(const ChromeFont& font); + + void SetColor(const SkColor& color); + + void SetKeyword(const std::wstring& keyword); + std::wstring keyword() const { return keyword_; } + + virtual void Paint(ChromeCanvas* canvas); + virtual void GetPreferredSize(CSize* out); + // The minimum size is just big enough to show the tab. + virtual void GetMinimumSize(CSize* out); + virtual void Layout(); + void DidChangeBounds(const CRect& previous, const CRect& current); + + void set_profile(Profile* profile) { profile_ = profile; } + + private: + ChromeViews::Label leading_label_; + ChromeViews::Label trailing_label_; + + // The keyword. + std::wstring keyword_; + + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(KeywordHintView); + }; + + + class ShowInfoBubbleTask; + class ShowFirstRunBubbleTask; + + // SecurityImageView is used to display the lock or warning icon when the + // current URL's scheme is https. + // + // If a message has been set with SetInfoBubbleText, it displays an info + // bubble when the mouse hovers on the image. + class SecurityImageView : public ChromeViews::ImageView, + public InfoBubbleDelegate { + public: + enum Image { + LOCK = 0, + WARNING + }; + + SecurityImageView(Profile* profile, ToolbarModel* model_); + virtual ~SecurityImageView(); + + // Sets the image that should be displayed. + void SetImageShown(Image image); + + // Overridden from view for the mouse hovering. + virtual void OnMouseMoved(const ChromeViews::MouseEvent& event); + virtual void OnMouseExited(const ChromeViews::MouseEvent& event); + virtual bool OnMousePressed(const ChromeViews::MouseEvent& event); + + // InfoBubbleDelegate + void InfoBubbleClosing(InfoBubble* info_bubble); + bool CloseOnEscape() { return true; } + + void set_profile(Profile* profile) { profile_ = profile; } + + private: + friend class ShowInfoBubbleTask; + + void ShowInfoBubble(); + + // The lock icon shown when using HTTPS. + static SkBitmap* lock_icon_; + + // The warning icon shown when HTTPS is broken. + static SkBitmap* warning_icon_; + + // The currently shown info bubble if any. + InfoBubble* info_bubble_; + + // A task used to display the info bubble when the mouse hovers on the image. + ShowInfoBubbleTask* show_info_bubble_task_; + + Profile* profile_; + + ToolbarModel* model_; + + DISALLOW_EVIL_CONSTRUCTORS(SecurityImageView); + }; + + // Both Layout and OnChanged call into this. This updates the contents + // of the 3 views: selected_keyword, keyword_hint and type_search_view. If + // force_layout is true, or one of these views has changed in such a way as + // to necessitate a layout, layout occurs as well. + void DoLayout(bool force_layout); + + // Returns the width in pixels of the contents of the edit. + int TextDisplayWidth(); + + // Returns true if the preferred size should be used for a view whose width + // is pref_width, the width of the text in the edit is text_width, and + // max_width is the maximum width of the edit. If this returns false, the + // minimum size of the view should be used. + bool UsePref(int pref_width, int text_width, int max_width); + + // Returns true if the view needs to be resized. This determines whether the + // min or pref should be used, and returns true if the view is not at that + // size. + bool NeedsResize(View* view, int text_width, int max_width); + + // Adjusts the keyword hint, selected keyword and type to search views + // based on the contents of the edit. Returns true if something changed that + // necessitates a layout. + bool AdjustHints(int text_width, int max_width); + + // If View fits in the specified region, it is made visible and the + // bounds are adjusted appropriately. If the View does not fit, it is + // made invisible. + void LayoutView(bool leading, ChromeViews::View* view, int text_width, + int max_width, gfx::Rect* bounds); + + // Sets the security icon to display. Note that no repaint is done. + void SetSecurityIcon(ToolbarModel::Icon icon); + + // Sets the text that should be displayed in the info label and its associated + // tooltip text. Call with an empty string if the info label should be + // hidden. + void SetInfoText(const std::wstring& text, + SkColor text_color, + const std::wstring& tooltip_text); + + // Sets the visibility of view to new_vis. Returns whether the visibility + // changed. + bool ToggleVisibility(bool new_vis, ChromeViews::View* view); + + // Helper for the Mouse event handlers that does all the real work. + void OnMouseEvent(const ChromeViews::MouseEvent& event, UINT msg); + + // Helper to show the first run info bubble. + void ShowFirstRunBubbleInternal(); + + // Current profile. Not owned by us. + Profile* profile_; + + // The Autocomplete Edit field. + scoped_ptr<AutocompleteEdit> location_entry_; + + // The command controller for this View. + CommandController* controller_; + + // The model. + ToolbarModel* model_; + + // Our delegate. + Delegate* delegate_; + + // This is the string of text from the autocompletion session that the user + // entered or selected. + std::wstring location_input_; + + // The user's desired disposition for how their input should be opened + WindowOpenDisposition disposition_; + + // The transition type to use for the navigation + PageTransition::Type transition_; + + // Font used by edit and some of the hints. + ChromeFont font_; + + // Location_entry view wrapper + ChromeViews::HWNDView* location_entry_view_; + + // The following views are used to provide hints and remind the user as to + // what is going in the edit. They are all added a children of the + // LocationBarView. At most one is visible at a time. Preference is + // given to the keyword_view_, then hint_view_, then type_to_search_view_. + + // Shown if the user has selected a keyword. + SelectedKeywordView selected_keyword_view_; + + // Shown if the selected url has a corresponding keyword. + KeywordHintView keyword_hint_view_; + + // Shown if the text is not a keyword or url. + ChromeViews::Label type_to_search_view_; + + // The view that shows the lock/warning when in HTTPS mode. + SecurityImageView security_image_view_; + + // A label displayed after the lock icon to show some extra information. + ChromeViews::Label info_label_; + + // When true, the location bar view is read only and also is has a slightly + // different presentation (font size / color). This is used for popups. + bool popup_window_mode_; + + // Used schedule a task for the first run info bubble. + ScopedRunnableMethodFactory<LocationBarView> first_run_bubble_; +}; + +#endif // CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__ diff --git a/chrome/browser/views/login_view.cc b/chrome/browser/views/login_view.cc new file mode 100644 index 0000000..28cc8d0 --- /dev/null +++ b/chrome/browser/views/login_view.cc @@ -0,0 +1,149 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> + +#include "chrome/browser/views/login_view.h" + +#include "base/message_loop.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/root_view.h" +#include "chrome/views/text_field.h" + +#include "generated_resources.h" + +namespace ChromeViews { + +static const int kMessageWidth = 320; +static const int kTextFieldStackHorizontalSpacing = 30; + +/////////////////////////////////////////////////////////////////////////////// +// LoginView, public: + +LoginView::LoginView(const std::wstring& explanation) + : username_field_(new TextField), + password_field_(new TextField(TextField::STYLE_PASSWORD)), + username_label_(new Label( + l10n_util::GetString(IDS_LOGIN_DIALOG_USERNAME_FIELD))), + password_label_(new Label( + l10n_util::GetString(IDS_LOGIN_DIALOG_PASSWORD_FIELD))), + message_label_(new Label(explanation)), + focus_grabber_factory_(this), + login_model_(NULL) { + message_label_->SetMultiLine(true); + message_label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + + // Initialize the Grid Layout Manager used for this dialog box. + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + // Add the column set for the information message at the top of the dialog + // box. + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, kMessageWidth, 0); + + // Add the column set for the user name and password fields and labels. + const int labels_column_set_id = 1; + column_set = layout->AddColumnSet(labels_column_set_id); + column_set->AddPaddingColumn(0, kTextFieldStackHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kTextFieldStackHorizontalSpacing); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(message_label_); + + layout->AddPaddingRow(0, kUnrelatedControlLargeVerticalSpacing); + + layout->StartRow(0, labels_column_set_id); + layout->AddView(username_label_); + layout->AddView(username_field_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, labels_column_set_id); + layout->AddView(password_label_); + layout->AddView(password_field_); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); +} + +LoginView::~LoginView() { + if (login_model_) + login_model_->SetObserver(NULL); +} + +std::wstring LoginView::GetUsername() { + return username_field_->GetText(); +} + +std::wstring LoginView::GetPassword() { + return password_field_->GetText(); +} + +void LoginView::SetModel(LoginModel* model) { + login_model_ = model; + if (login_model_) + login_model_->SetObserver(this); +} +/////////////////////////////////////////////////////////////////////////////// +// LoginView, ChromeViews::View, ChromeViews::LoginModelObserver overrides: + +void LoginView::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (is_add && child == this) { + MessageLoop::current()->PostTask(FROM_HERE, + focus_grabber_factory_.NewRunnableMethod(&LoginView::FocusFirstField)); + } +} + +void LoginView::OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) { + if (username_field_->GetText().empty()) { + username_field_->SetText(username); + password_field_->SetText(password); + username_field_->SelectAll(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// LoginView, private: + +void LoginView::FocusFirstField() { + username_field_->RequestFocus(); +} + +} // namespace diff --git a/chrome/browser/views/login_view.h b/chrome/browser/views/login_view.h new file mode 100644 index 0000000..93c3003 --- /dev/null +++ b/chrome/browser/views/login_view.h @@ -0,0 +1,108 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_LOGIN_VIEW_H__ +#define CHROME_BROWSER_VIEWS_LOGIN_VIEW_H__ + +#include "base/task.h" +#include "chrome/views/view.h" + +namespace ChromeViews { +class Label; +class TextField; +class LoginModel; + +// Simple Model & Observer interfaces for a LoginView to facilitate exchanging +// information. +class LoginModelObserver { + public: + // Called by the model when a username,password pair has been identified + // as a match for the pending login prompt. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) = 0; +}; + +class LoginModel { + public: + // Set the observer interested in the data from the model. + // observer can be null, signifying there is no longer any observer + // interested in the data. + virtual void SetObserver(LoginModelObserver* observer) = 0; +}; + +// This class is responsible for displaying the contents of a login window +// for HTTP/FTP authentication. +class LoginView : public View, public LoginModelObserver { + public: + explicit LoginView(const std::wstring& explanation); + virtual ~LoginView(); + + // Access the data in the username/password text fields. + std::wstring GetUsername(); + std::wstring GetPassword(); + + // LoginModelObserver implementation. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password); + + // Sets the model. This lets the observer notify the model + // when it has been closed / freed, so the model should no longer try and + // contact it. The view does not own the model, and it is the responsibility + // of the caller to inform this view if the model is deleted. + void SetModel(LoginModel* model); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + private: + void FocusFirstField(); + + // Non-owning refs to the input text fields. + TextField* username_field_; + TextField* password_field_; + + // Button labels + Label* username_label_; + Label* password_label_; + + // Authentication message. + Label* message_label_; + + // If not null, points to a model we need to notify of our own destruction + // so it doesn't try and access this when its too late. + LoginModel* login_model_; + + ScopedRunnableMethodFactory<LoginView> focus_grabber_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(LoginView); +}; + +} // namespace +#endif // CHROME_BROWSER_VIEWS_LOGIN_VIEW_H__ diff --git a/chrome/browser/views/options/advanced_contents_view.cc b/chrome/browser/views/options/advanced_contents_view.cc new file mode 100644 index 0000000..32359a9 --- /dev/null +++ b/chrome/browser/views/options/advanced_contents_view.cc @@ -0,0 +1,1203 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/advanced_contents_view.h" + +#include <windows.h> + +#include <cryptuiapi.h> +#pragma comment(lib, "cryptui.lib") +#include <shellapi.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/gfx/native_theme.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/download_manager.h" +#include "chrome/browser/gears_integration.h" +#include "chrome/browser/metrics_service.h" +#include "chrome/browser/net/dns_global.h" +#include "chrome/browser/resource_dispatcher_host.h" +#include "chrome/browser/safe_browsing/safe_browsing_service.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/restart_message_box.h" +#include "chrome/browser/views/options/cookies_view.h" +#include "chrome/browser/views/options/language_combobox_model.h" +#include "chrome/common/filter_policy.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/pref_member.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/scroll_view.h" +#include "net/base/ssl_config_service.h" + +#include "generated_resources.h" + +namespace { + +// A background object that paints the scrollable list background, +// which may be rendered by the system visual styles system. +class ListBackground : public ChromeViews::Background { + public: + explicit ListBackground() { + SkColor list_color = + gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::LIST, 1, TS_NORMAL, TMT_FILLCOLOR, COLOR_WINDOW); + SetNativeControlColor(list_color); + } + virtual ~ListBackground() {} + + virtual void Paint(ChromeCanvas* canvas, ChromeViews::View* view) const { + HDC dc = canvas->beginPlatformPaint(); + CRect lb; + view->GetLocalBounds(&lb, true); + gfx::NativeTheme::instance()->PaintListBackground(dc, true, &lb); + canvas->endPlatformPaint(); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ListBackground); +}; + +} // namespace +//////////////////////////////////////////////////////////////////////////////// +// AdvancedSection +// A convenience view for grouping advanced options together into related +// sections. +// +class AdvancedSection : public OptionsPageView { + public: + AdvancedSection(Profile* profile, const std::wstring& title); + virtual ~AdvancedSection() {} + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + + protected: + // Convenience helpers to add different kinds of ColumnSets for specific + // types of layout. + void AddWrappingColumnSet(ChromeViews::GridLayout* layout, int id); + void AddDependentTwoColumnSet(ChromeViews::GridLayout* layout, int id); + void AddTwoColumnSet(ChromeViews::GridLayout* layout, int id); + void AddIndentedColumnSet(ChromeViews::GridLayout* layout, int id); + + // Convenience helpers for adding controls to specific layouts in an + // aesthetically pleasing way. + void AddWrappingCheckboxRow(ChromeViews::GridLayout* layout, + ChromeViews::CheckBox* checkbox, + int id, + bool related_follows); + void AddWrappingLabelRow(ChromeViews::GridLayout* layout, + ChromeViews::Label* label, + int id, + bool related_follows); + void AddTwoColumnRow(ChromeViews::GridLayout* layout, + ChromeViews::Label* label, + ChromeViews::View* control, + bool control_stretches, // Whether or not the control + // expands to fill the width. + int id, + bool related_follows); + void AddLeadingControl(ChromeViews::GridLayout* layout, + ChromeViews::View* control, + int id, + bool related_follows); + void AddIndentedControl(ChromeViews::GridLayout* layout, + ChromeViews::View* control, + int id, + bool related_follows); + void AddSpacing(ChromeViews::GridLayout* layout, bool related_follows); + + // OptionsPageView overrides: + virtual void InitControlLayout(); + + // The View that contains the contents of the section. + ChromeViews::View* contents_; + + private: + // The section title. + ChromeViews::Label* title_label_; + + DISALLOW_EVIL_CONSTRUCTORS(AdvancedSection); +}; + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedSection, public: + +AdvancedSection::AdvancedSection(Profile* profile, + const std::wstring& title) + : contents_(NULL), + title_label_(new ChromeViews::Label(title)), + OptionsPageView(profile) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont title_font = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(0, ChromeFont::BOLD); + title_label_->SetFont(title_font); + + SkColor title_color = gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::BUTTON, BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, + COLOR_WINDOWTEXT); + title_label_->SetColor(title_color); +} + +void AdvancedSection::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); + contents_->Layout(); +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedSection, protected: + +void AdvancedSection::AddWrappingColumnSet(ChromeViews::GridLayout* layout, + int id) { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + ColumnSet* column_set = layout->AddColumnSet(id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); +} + +void AdvancedSection::AddDependentTwoColumnSet(ChromeViews::GridLayout* layout, + int id) { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + ColumnSet* column_set = layout->AddColumnSet(id); + column_set->AddPaddingColumn(0, ChromeViews::CheckBox::GetTextIndent()); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing); +} + +void AdvancedSection::AddTwoColumnSet(ChromeViews::GridLayout* layout, + int id) { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + ColumnSet* column_set = layout->AddColumnSet(id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); +} + +void AdvancedSection::AddIndentedColumnSet(ChromeViews::GridLayout* layout, + int id) { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + ColumnSet* column_set = layout->AddColumnSet(id); + column_set->AddPaddingColumn(0, ChromeViews::CheckBox::GetTextIndent()); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); +} + +void AdvancedSection::AddWrappingCheckboxRow(ChromeViews::GridLayout* layout, + ChromeViews::CheckBox* checkbox, + int id, + bool related_follows) { + checkbox->SetMultiLine(true); + layout->StartRow(0, id); + layout->AddView(checkbox); + AddSpacing(layout, related_follows); +} + +void AdvancedSection::AddWrappingLabelRow(ChromeViews::GridLayout* layout, + ChromeViews::Label* label, + int id, + bool related_follows) { + label->SetMultiLine(true); + label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + layout->StartRow(0, id); + layout->AddView(label); + AddSpacing(layout, related_follows); +} + +void AdvancedSection::AddTwoColumnRow(ChromeViews::GridLayout* layout, + ChromeViews::Label* label, + ChromeViews::View* control, + bool control_stretches, + int id, + bool related_follows) { + label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + layout->StartRow(0, id); + layout->AddView(label); + if (control_stretches) { + layout->AddView(control); + } else { + layout->AddView(control, 1, 1, ChromeViews::GridLayout::LEADING, + ChromeViews::GridLayout::CENTER); + } + AddSpacing(layout, related_follows); +} + +void AdvancedSection::AddLeadingControl(ChromeViews::GridLayout* layout, + ChromeViews::View* control, + int id, + bool related_follows) { + using ChromeViews::GridLayout; + layout->StartRow(0, id); + layout->AddView(control, 1, 1, GridLayout::LEADING, GridLayout::CENTER); + AddSpacing(layout, related_follows); +} + +void AdvancedSection::AddSpacing(ChromeViews::GridLayout* layout, + bool related_follows) { + layout->AddPaddingRow(0, related_follows ? kRelatedControlVerticalSpacing + : kUnrelatedControlVerticalSpacing); +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedSection, OptionsPageView overrides: + +void AdvancedSection::InitControlLayout() { + contents_ = new ChromeViews::View; + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(this); + SetLayoutManager(layout); + + const int single_column_layout_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_layout_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + const int inset_column_layout_id = 1; + column_set = layout->AddColumnSet(inset_column_layout_id); + column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_layout_id); + layout->AddView(title_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, inset_column_layout_id); + layout->AddView(contents_); +} + +//////////////////////////////////////////////////////////////////////////////// +// GeneralSection + +class GeneralSection : public AdvancedSection, + public ChromeViews::NativeButton::Listener, + public ChromeViews::LinkController { + public: + explicit GeneralSection(Profile* profile); + virtual ~GeneralSection() {} + + // Overridden from ChromeViews::NativeButton::Listener: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Overridden from ChromeViews::LinkController: + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + // Overridden from ChromeViews::View: + virtual void Layout(); + + protected: + // OptionsPageView overrides: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + private: + // Controls for this section: + ChromeViews::Label* reset_file_handlers_label_; + ChromeViews::NativeButton* reset_file_handlers_button_; + ChromeViews::CheckBox* reporting_enabled_checkbox_; + ChromeViews::Link* learn_more_link_; + + // Preferences for this section: + StringPrefMember auto_open_files_; + BooleanPrefMember enable_metrics_recording_; + + DISALLOW_EVIL_CONSTRUCTORS(GeneralSection); +}; + +GeneralSection::GeneralSection(Profile* profile) + : reset_file_handlers_label_(NULL), + reset_file_handlers_button_(NULL), + reporting_enabled_checkbox_(NULL), + learn_more_link_(NULL), + AdvancedSection(profile, + l10n_util::GetString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_GENERAL)) { +} + +void GeneralSection::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == reset_file_handlers_button_) { + profile()->GetDownloadManager()->ResetAutoOpenFiles(); + UserMetricsRecordAction(L"Options_ResetAutoOpenFiles", + profile()->GetPrefs()); + } else if (sender == reporting_enabled_checkbox_) { + bool enabled = reporting_enabled_checkbox_->IsSelected(); + // Do what we can, but we might not be able to get what was asked for. + bool done = g_browser_process->metrics_service()->EnableReporting(enabled); + if (!done) { + enabled = !enabled; + done = g_browser_process->metrics_service()->EnableReporting(enabled); + DCHECK(done); + reporting_enabled_checkbox_->SetIsSelected(enabled); + } else { + if (enabled) { + UserMetricsRecordAction(L"Options_MetricsReportingCheckbox_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_MetricsReportingCheckbox_Disable", + profile()->GetPrefs()); + } + RestartMessageBox::ShowMessageBox(GetRootWindow()); + } + enable_metrics_recording_.SetValue(enabled); + } +} + +void GeneralSection::LinkActivated(ChromeViews::Link* source, + int event_flags) { + if (source == learn_more_link_) { + // We open a new browser window so the Options dialog doesn't get lost + // behind other windows. + Browser* browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile(), + BrowserType::TABBED_BROWSER, + std::wstring()); + browser->OpenURL( + GURL(l10n_util::GetString(IDS_LEARN_MORE_HELPMAKECHROMEBETTER_URL)), + NEW_WINDOW, + PageTransition::LINK); + } +} + +void GeneralSection::Layout() { + // We override this to try and set the width of the enable logging checkbox + // to the width of the parent less some fudging since the checkbox's + // preferred size calculation code is dependent on its width, and if we don't + // do this then it will return 0 as a preferred width when GridLayout (called + // from View::Layout) tries to access it. + ChromeViews::View* parent = GetParent(); + if (parent && parent->GetWidth()) { + const int parent_width = parent->GetWidth(); + reporting_enabled_checkbox_->SetBounds(0, 0, parent_width - 20, 0); + } + View::Layout(); +} + +void GeneralSection::InitControlLayout() { + AdvancedSection::InitControlLayout(); + + reset_file_handlers_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_AUTOOPENFILETYPES_INFO)); + reset_file_handlers_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_AUTOOPENFILETYPES_RESETTODEFAULT)); + reset_file_handlers_button_->SetListener(this); + reporting_enabled_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_ENABLE_LOGGING)); + reporting_enabled_checkbox_->SetMultiLine(true); + reporting_enabled_checkbox_->SetListener(this); + learn_more_link_ = + new ChromeViews::Link(l10n_util::GetString(IDS_LEARN_MORE)); + learn_more_link_->SetController(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + GridLayout* layout = new GridLayout(contents_); + contents_->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + AddWrappingColumnSet(layout, single_column_view_set_id); + const int dependent_labeled_field_set_id = 1; + AddDependentTwoColumnSet(layout, dependent_labeled_field_set_id); + const int indented_view_set_id = 2; + AddIndentedColumnSet(layout, indented_view_set_id); + + // File Handlers. + AddWrappingLabelRow(layout, reset_file_handlers_label_, + single_column_view_set_id, true); + AddLeadingControl(layout, reset_file_handlers_button_, indented_view_set_id, + false); + AddLeadingControl(layout, reporting_enabled_checkbox_, + single_column_view_set_id, true); + AddLeadingControl(layout, learn_more_link_, indented_view_set_id, false); + + // Init member prefs so we can update the controls if prefs change. + auto_open_files_.Init(prefs::kDownloadExtensionsToOpen, profile()->GetPrefs(), + this); + enable_metrics_recording_.Init(prefs::kMetricsReportingEnabled, + g_browser_process->local_state(), this); +} + +void GeneralSection::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kDownloadExtensionsToOpen) { + bool enabled = + profile()->GetDownloadManager()->HasAutoOpenFileTypesRegistered(); + reset_file_handlers_label_->SetEnabled(enabled); + reset_file_handlers_button_->SetEnabled(enabled); + } + if (!pref_name ||*pref_name == prefs::kMetricsReportingEnabled) { + bool enabled = enable_metrics_recording_.GetValue(); + bool done = g_browser_process->metrics_service()->EnableReporting(enabled); + if (!done) { + enabled = !enabled; + done = g_browser_process->metrics_service()->EnableReporting(enabled); + DCHECK(done); + } + reporting_enabled_checkbox_->SetIsSelected(enabled); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ContentSection + +class ContentSection : public AdvancedSection, + public ChromeViews::NativeButton::Listener { + public: + explicit ContentSection(Profile* profile); + virtual ~ContentSection() {} + + // Overridden from ChromeViews::NativeButton::Listener: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + protected: + // OptionsPageView overrides: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + private: + // Controls for this section: + ChromeViews::CheckBox* popup_blocked_notification_checkbox_; + ChromeViews::Label* gears_label_; + ChromeViews::NativeButton* gears_settings_button_; + + BooleanPrefMember disable_popup_blocked_notification_pref_; + + DISALLOW_EVIL_CONSTRUCTORS(ContentSection); +}; + +ContentSection::ContentSection(Profile* profile) + : popup_blocked_notification_checkbox_(NULL), + gears_label_(NULL), + gears_settings_button_(NULL), + AdvancedSection(profile, + l10n_util::GetString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_CONTENT)) { +} + +void ContentSection::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == popup_blocked_notification_checkbox_) { + bool notification_disabled = + popup_blocked_notification_checkbox_->IsSelected(); + if (notification_disabled) { + UserMetricsRecordAction(L"Options_BlockAllPopups_Disable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_BlockAllPopups_Enable", + profile()->GetPrefs()); + } + disable_popup_blocked_notification_pref_.SetValue(!notification_disabled); + } else if (sender == gears_settings_button_) { + UserMetricsRecordAction(L"Options_GearsSettings", NULL); + GearsSettingsPressed(GetAncestor(GetViewContainer()->GetHWND(), GA_ROOT)); + } +} + +void ContentSection::InitControlLayout() { + AdvancedSection::InitControlLayout(); + + popup_blocked_notification_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_SHOWPOPUPBLOCKEDNOTIFICATION)); + popup_blocked_notification_checkbox_->SetListener(this); + + gears_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_GEARSSETTINGS_GROUP_NAME)); + gears_settings_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_GEARSSETTINGS_CONFIGUREGEARS_BUTTON)); + gears_settings_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + GridLayout* layout = new GridLayout(contents_); + contents_->SetLayoutManager(layout); + + const int col_id = 0; + AddWrappingColumnSet(layout, col_id); + const int two_col_id = 1; + AddTwoColumnSet(layout, two_col_id); + + AddWrappingCheckboxRow(layout, popup_blocked_notification_checkbox_, + col_id, true); + AddTwoColumnRow(layout, gears_label_, gears_settings_button_, false, + two_col_id, false); + + // Init member prefs so we can update the controls if prefs change. + disable_popup_blocked_notification_pref_.Init(prefs::kBlockPopups, + profile()->GetPrefs(), this); +} + +void ContentSection::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kBlockPopups) { + popup_blocked_notification_checkbox_->SetIsSelected( + !disable_popup_blocked_notification_pref_.GetValue()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// SecuritySection + +class MixedContentComboModel : public ChromeViews::ComboBox::Model { + public: + MixedContentComboModel() {} + + // Return the number of items in the combo box. + virtual int GetItemCount(ChromeViews::ComboBox* source) { + return 3; + } + + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index) { + const int kStringIDs[] = { + IDS_OPTIONS_INCLUDE_MIXED_CONTENT, + IDS_OPTIONS_INCLUDE_MIXED_CONTENT_IMAGE_ONLY, + IDS_OPTIONS_INCLUDE_NO_MIXED_CONTENT + }; + if (index >= 0 && index < arraysize(kStringIDs)) + return l10n_util::GetString(kStringIDs[index]); + + NOTREACHED(); + return L""; + } + + static int FilterPolicyToIndex(FilterPolicy::Type policy) { + return policy; + } + + static FilterPolicy::Type IndexToFilterPolicy(int index) { + if (FilterPolicy::ValidType(index)) + return FilterPolicy::FromInt(index); + + NOTREACHED(); + return FilterPolicy::DONT_FILTER; + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(MixedContentComboModel); +}; + +class CookieBehaviorComboModel : public ChromeViews::ComboBox::Model { + public: + CookieBehaviorComboModel() {} + + // Return the number of items in the combo box. + virtual int GetItemCount(ChromeViews::ComboBox* source) { + return 3; + } + + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index) { + const int kStringIDs[] = { + IDS_OPTIONS_COOKIES_ACCEPT_ALL_COOKIES, + IDS_OPTIONS_COOKIES_RESTRICT_THIRD_PARTY_COOKIES, + IDS_OPTIONS_COOKIES_BLOCK_ALL_COOKIES + }; + if (index >= 0 && index < arraysize(kStringIDs)) + return l10n_util::GetString(kStringIDs[index]); + + NOTREACHED(); + return L""; + } + + static int CookiePolicyToIndex(CookiePolicy::Type policy) { + return policy; + } + + static CookiePolicy::Type IndexToCookiePolicy(int index) { + if (CookiePolicy::ValidType(index)) + return CookiePolicy::FromInt(index); + + NOTREACHED(); + return CookiePolicy::ALLOW_ALL_COOKIES; + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(CookieBehaviorComboModel); +}; + +class SecuritySection : public AdvancedSection, + public ChromeViews::NativeButton::Listener, + public ChromeViews::ComboBox::Listener { + public: + explicit SecuritySection(Profile* profile); + virtual ~SecuritySection() {} + + // Overridden from ChromeViews::NativeButton::Listener: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Overridden from ChromeViews::ComboBox::Listener: + virtual void ItemChanged(ChromeViews::ComboBox* sender, + int prev_index, + int new_index); + + protected: + // OptionsPageView overrides: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + private: + // Controls for this section: + ChromeViews::CheckBox* enable_safe_browsing_checkbox_; + ChromeViews::Label* ssl_info_label_; + ChromeViews::CheckBox* enable_ssl2_checkbox_; + ChromeViews::CheckBox* check_for_cert_revocation_checkbox_; + ChromeViews::Label* mixed_content_info_label_; + ChromeViews::ComboBox* mixed_content_combobox_; + ChromeViews::Label* manage_certificates_label_; + ChromeViews::NativeButton* manage_certificates_button_; + ChromeViews::Label* cookie_behavior_label_; + ChromeViews::ComboBox* cookie_behavior_combobox_; + ChromeViews::NativeButton* show_cookies_button_; + + // The contents of the mixed content combobox. + scoped_ptr<MixedContentComboModel> mixed_content_model_; + + // Dummy for now. Used to populate cookies models. + scoped_ptr<CookieBehaviorComboModel> allow_cookies_model_; + + BooleanPrefMember safe_browsing_; + IntegerPrefMember filter_mixed_content_; + IntegerPrefMember cookie_behavior_; + + DISALLOW_EVIL_CONSTRUCTORS(SecuritySection); +}; + +SecuritySection::SecuritySection(Profile* profile) + : enable_safe_browsing_checkbox_(NULL), + ssl_info_label_(NULL), + enable_ssl2_checkbox_(NULL), + check_for_cert_revocation_checkbox_(NULL), + mixed_content_info_label_(NULL), + mixed_content_combobox_(NULL), + manage_certificates_label_(NULL), + manage_certificates_button_(NULL), + cookie_behavior_label_(NULL), + cookie_behavior_combobox_(NULL), + show_cookies_button_(NULL), + AdvancedSection(profile, + l10n_util::GetString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_SECURITY)) { +} + +void SecuritySection::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == enable_safe_browsing_checkbox_) { + bool enabled = enable_safe_browsing_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_SafeBrowsingCheckbox_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_SafeBrowsingCheckbox_Disable", + profile()->GetPrefs()); + } + safe_browsing_.SetValue(enabled); + SafeBrowsingService* safe_browsing_service = + g_browser_process->resource_dispatcher_host()->safe_browsing_service(); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + safe_browsing_service, &SafeBrowsingService::OnEnable, enabled)); + } else if (sender == enable_ssl2_checkbox_) { + bool enabled = enable_ssl2_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_SSL2_Enable", NULL); + } else { + UserMetricsRecordAction(L"Options_SSL2_Disable", NULL); + } + net::SSLConfigService::SetSSL2Enabled(enabled); + } else if (sender == check_for_cert_revocation_checkbox_) { + bool enabled = check_for_cert_revocation_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_CheckCertRevocation_Enable", NULL); + } else { + UserMetricsRecordAction(L"Options_CheckCertRevocation_Disable", NULL); + } + net::SSLConfigService::SetRevCheckingEnabled(enabled); + } else if (sender == manage_certificates_button_) { + UserMetricsRecordAction(L"Options_ManagerCerts", NULL); + CRYPTUI_CERT_MGR_STRUCT cert_mgr = { 0 }; + cert_mgr.dwSize = sizeof(CRYPTUI_CERT_MGR_STRUCT); + cert_mgr.hwndParent = GetRootWindow(); + ::CryptUIDlgCertMgr(&cert_mgr); + } else if (sender == show_cookies_button_) { + UserMetricsRecordAction(L"Options_ShowCookies", NULL); + CookiesView::ShowCookiesWindow(profile()); + } +} + +void SecuritySection::ItemChanged(ChromeViews::ComboBox* sender, + int prev_index, + int new_index) { + if (sender == mixed_content_combobox_) { + // TODO(jcampan): bug #1112812: change this to the real enum once we have + // piped the images only filtering. + FilterPolicy::Type filter_policy = + MixedContentComboModel::IndexToFilterPolicy(new_index); + const wchar_t* kUserMetrics[] = { + L"Options_FilterNone", + L"Options_FilterAllExceptImages", + L"Options_FilterAll" + }; + DCHECK(filter_policy >= 0 && filter_policy < arraysize(kUserMetrics)); + UserMetricsRecordAction(kUserMetrics[filter_policy], profile()->GetPrefs()); + filter_mixed_content_.SetValue(filter_policy); + } else if (sender == cookie_behavior_combobox_) { + CookiePolicy::Type cookie_policy = + CookieBehaviorComboModel::IndexToCookiePolicy(new_index); + const wchar_t* kUserMetrics[] = { + L"Options_AllowAllCookies", + L"Options_BlockThirdPartyCookies", + L"Options_BlockAllCookies" + }; + DCHECK(cookie_policy >= 0 && cookie_policy < arraysize(kUserMetrics)); + UserMetricsRecordAction(kUserMetrics[cookie_policy], profile()->GetPrefs()); + this->cookie_behavior_.SetValue(cookie_policy); + } +} + +void SecuritySection::InitControlLayout() { + AdvancedSection::InitControlLayout(); + + enable_safe_browsing_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_SAFEBROWSING_ENABLEPROTECTION)); + enable_safe_browsing_checkbox_->SetListener(this); + ssl_info_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_SSL_GROUP_DESCRIPTION)); + enable_ssl2_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_SSL_USESSL2)); + enable_ssl2_checkbox_->SetListener(this); + check_for_cert_revocation_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_SSL_CHECKREVOCATION)); + check_for_cert_revocation_checkbox_->SetListener(this); + mixed_content_info_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_MIXED_CONTENT_LABEL)); + mixed_content_model_.reset(new MixedContentComboModel); + mixed_content_combobox_ = new ChromeViews::ComboBox( + mixed_content_model_.get()); + mixed_content_combobox_->SetListener(this); + manage_certificates_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_CERTIFICATES_LABEL)); + manage_certificates_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_CERTIFICATES_MANAGE_BUTTON)); + manage_certificates_button_->SetListener(this); + cookie_behavior_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_COOKIES_ACCEPT_LABEL)); + allow_cookies_model_.reset(new CookieBehaviorComboModel); + cookie_behavior_combobox_ = new ChromeViews::ComboBox( + allow_cookies_model_.get()); + cookie_behavior_combobox_->SetListener(this); + show_cookies_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_COOKIES_SHOWCOOKIES)); + show_cookies_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + GridLayout* layout = new GridLayout(contents_); + contents_->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + AddWrappingColumnSet(layout, single_column_view_set_id); + const int dependent_labeled_field_set_id = 1; + AddDependentTwoColumnSet(layout, dependent_labeled_field_set_id); + const int double_column_view_set_id = 2; + AddTwoColumnSet(layout, double_column_view_set_id); + const int indented_column_set_id = 3; + AddIndentedColumnSet(layout, indented_column_set_id); + + // Safe browsing controls. + AddWrappingCheckboxRow(layout, enable_safe_browsing_checkbox_, + single_column_view_set_id, false); + + // SSL connection controls and Certificates. + AddWrappingLabelRow(layout, manage_certificates_label_, + single_column_view_set_id, true); + AddLeadingControl(layout, manage_certificates_button_, + indented_column_set_id, false); + AddWrappingLabelRow(layout, ssl_info_label_, single_column_view_set_id, + true); + AddWrappingCheckboxRow(layout, enable_ssl2_checkbox_, + indented_column_set_id, true); + AddWrappingCheckboxRow(layout, check_for_cert_revocation_checkbox_, + indented_column_set_id, false); + + // Mixed content controls. + AddWrappingLabelRow(layout, mixed_content_info_label_, + single_column_view_set_id, true); + AddLeadingControl(layout, mixed_content_combobox_, + indented_column_set_id, false); + + // Cookies. + AddWrappingLabelRow(layout, cookie_behavior_label_, single_column_view_set_id, + true); + AddLeadingControl(layout, cookie_behavior_combobox_, indented_column_set_id, + true); + AddLeadingControl(layout, show_cookies_button_, indented_column_set_id, + false); + + // Init member prefs so we can update the controls if prefs change. + safe_browsing_.Init(prefs::kSafeBrowsingEnabled, profile()->GetPrefs(), this); + filter_mixed_content_.Init(prefs::kMixedContentFiltering, + profile()->GetPrefs(), this); + cookie_behavior_.Init(prefs::kCookieBehavior, profile()->GetPrefs(), this); +} + +// This method is called with a null pref_name when the dialog is initialized. +void SecuritySection::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kMixedContentFiltering) { + mixed_content_combobox_->SetSelectedItem( + MixedContentComboModel::FilterPolicyToIndex( + FilterPolicy::FromInt(filter_mixed_content_.GetValue()))); + } + if (!pref_name || *pref_name == prefs::kCookieBehavior) { + cookie_behavior_combobox_->SetSelectedItem( + CookieBehaviorComboModel::CookiePolicyToIndex( + CookiePolicy::FromInt(cookie_behavior_.GetValue()))); + } + if (!pref_name || *pref_name == prefs::kSafeBrowsingEnabled) + enable_safe_browsing_checkbox_->SetIsSelected(safe_browsing_.GetValue()); + // These SSL options are system settings and stored in the OS. + if (!pref_name) { + net::SSLConfig config; + if (net::SSLConfigService::GetSSLConfigNow(&config)) { + enable_ssl2_checkbox_->SetIsSelected(config.ssl2_enabled); + check_for_cert_revocation_checkbox_->SetIsSelected( + config.rev_checking_enabled); + } else { + enable_ssl2_checkbox_->SetEnabled(false); + check_for_cert_revocation_checkbox_->SetEnabled(false); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NetworkSection + +namespace { + +// A helper method that opens the Internet Options control panel dialog with +// the Connections tab selected. +class OpenConnectionDialogTask : public Task { + public: + OpenConnectionDialogTask() {} + + virtual void Run() { + // Using rundll32 seems better than LaunchConnectionDialog which causes a + // new dialog to be made for each call. rundll32 uses the same global + // dialog and it seems to share with the shortcut in control panel. + std::wstring rundll32; + PathService::Get(base::DIR_SYSTEM, &rundll32); + file_util::AppendToPath(&rundll32, L"rundll32.exe"); + + std::wstring shell32dll; + PathService::Get(base::DIR_SYSTEM, &shell32dll); + file_util::AppendToPath(&shell32dll, L"shell32.dll"); + + std::wstring inetcpl; + PathService::Get(base::DIR_SYSTEM, &inetcpl); + file_util::AppendToPath(&inetcpl, L"inetcpl.cpl,,4"); + + std::wstring args(shell32dll); + args.append(L",Control_RunDLL "); + args.append(inetcpl); + + ShellExecute(NULL, L"open", rundll32.c_str(), args.c_str(), NULL, + SW_SHOWNORMAL); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(OpenConnectionDialogTask); +}; + +} // namespace + +class NetworkSection : public AdvancedSection, + public ChromeViews::NativeButton::Listener { + public: + explicit NetworkSection(Profile* profile); + virtual ~NetworkSection() {} + + // Overridden from ChromeViews::NativeButton::Listener: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + protected: + // OptionsPageView overrides: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + private: + // Controls for this section: + ChromeViews::Label* change_proxies_label_; + ChromeViews::NativeButton* change_proxies_button_; + ChromeViews::CheckBox* enable_link_doctor_checkbox_; + ChromeViews::CheckBox* enable_dns_prefetching_checkbox_; + + BooleanPrefMember alternate_error_pages_; + BooleanPrefMember dns_prefetch_enabled_; + + DISALLOW_EVIL_CONSTRUCTORS(NetworkSection); +}; + +NetworkSection::NetworkSection(Profile* profile) + : change_proxies_label_(NULL), + change_proxies_button_(NULL), + enable_link_doctor_checkbox_(NULL), + enable_dns_prefetching_checkbox_(NULL), + AdvancedSection(profile, + l10n_util::GetString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_NETWORK)) { +} + +void NetworkSection::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == change_proxies_button_) { + UserMetricsRecordAction(L"Options_ChangeProxies", NULL); + Thread* thread = g_browser_process->file_thread(); + DCHECK(thread); + thread->message_loop()->PostTask(FROM_HERE, new OpenConnectionDialogTask); + } else if (sender == enable_link_doctor_checkbox_) { + bool enabled = enable_link_doctor_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_LinkDoctorCheckbox_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_LinkDoctorCheckbox_Disable", + profile()->GetPrefs()); + } + alternate_error_pages_.SetValue(enabled); + } else if (sender == enable_dns_prefetching_checkbox_) { + bool enabled = enable_dns_prefetching_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_DnsPrefetchCheckbox_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_DnsPrefetchCheckbox_Disable", + profile()->GetPrefs()); + } + dns_prefetch_enabled_.SetValue(enabled); + chrome_browser_net::EnableDnsPrefetch(enabled); + } +} + +void NetworkSection::InitControlLayout() { + AdvancedSection::InitControlLayout(); + + change_proxies_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_PROXIES_LABEL)); + change_proxies_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON)); + change_proxies_button_->SetListener(this); + enable_link_doctor_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_LINKDOCTOR_PREF)); + enable_link_doctor_checkbox_->SetListener(this); + enable_dns_prefetching_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_NETWORK_DNS_PREFETCH_ENABLED_DESCRIPTION)); + enable_dns_prefetching_checkbox_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + GridLayout* layout = new GridLayout(contents_); + contents_->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + AddWrappingColumnSet(layout, single_column_view_set_id); + const int indented_view_set_id = 1; + AddIndentedColumnSet(layout, indented_view_set_id); + const int dependent_labeled_field_set_id = 2; + AddDependentTwoColumnSet(layout, dependent_labeled_field_set_id); + const int dns_set_id = 3; + AddDependentTwoColumnSet(layout, dns_set_id); + + // Proxy settings. + AddWrappingLabelRow(layout, change_proxies_label_, single_column_view_set_id, + true); + AddLeadingControl(layout, change_proxies_button_, indented_view_set_id, + false); + + // Link doctor. + AddWrappingCheckboxRow(layout, enable_link_doctor_checkbox_, + single_column_view_set_id, true); + + // DNS pre-fetching. + AddWrappingCheckboxRow(layout, enable_dns_prefetching_checkbox_, + single_column_view_set_id, false); + + // Init member prefs so we can update the controls if prefs change. + alternate_error_pages_.Init(prefs::kAlternateErrorPagesEnabled, + profile()->GetPrefs(), this); + dns_prefetch_enabled_.Init(prefs::kDnsPrefetchingEnabled, + profile()->GetPrefs(), this); +} + +void NetworkSection::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kAlternateErrorPagesEnabled) { + enable_link_doctor_checkbox_->SetIsSelected( + alternate_error_pages_.GetValue()); + } + if (!pref_name || *pref_name == prefs::kDnsPrefetchingEnabled) { + bool enabled = dns_prefetch_enabled_.GetValue(); + enable_dns_prefetching_checkbox_->SetIsSelected(enabled); + chrome_browser_net::EnableDnsPrefetch(enabled); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedContentsView + +class AdvancedContentsView : public OptionsPageView { + public: + explicit AdvancedContentsView(Profile* profile); + virtual ~AdvancedContentsView(); + + // ChromeViews::View overrides: + virtual int GetLineScrollIncrement(ChromeViews::ScrollView* scroll_view, + bool is_horizontal, bool is_positive); + void Layout(); + + protected: + // OptionsPageView implementation: + virtual void InitControlLayout(); + + private: + static void InitClass(); + + static int line_height_; + + DISALLOW_EVIL_CONSTRUCTORS(AdvancedContentsView); +}; + +// static +int AdvancedContentsView::line_height_ = 0; + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedContentsView, public: + +AdvancedContentsView::AdvancedContentsView(Profile* profile) + : OptionsPageView(profile) { + InitClass(); +} + +AdvancedContentsView::~AdvancedContentsView() { +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedContentsView, ChromeViews::View overrides: + +int AdvancedContentsView::GetLineScrollIncrement( + ChromeViews::ScrollView* scroll_view, + bool is_horizontal, + bool is_positive) { + + if (!is_horizontal) + return line_height_; + return View::GetPageScrollIncrement(scroll_view, is_horizontal, is_positive); +} + +void AdvancedContentsView::Layout() { + ChromeViews::View* parent = GetParent(); + if (parent && parent->GetWidth()) { + const int width = parent->GetWidth(); + const int height = GetHeightForWidth(width); + SetBounds(0, 0, width, height); + } else { + CSize pref; + GetPreferredSize(&pref); + SetBounds(0, 0, pref.cx, pref.cy); + } + View::Layout(); +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedContentsView, OptionsPageView implementation: + +void AdvancedContentsView::InitControlLayout() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(new GeneralSection(profile())); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(new NetworkSection(profile())); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(new ContentSection(profile())); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(new SecuritySection(profile())); +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedContentsView, private: + +void AdvancedContentsView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + line_height_ = rb.GetFont(ResourceBundle::BaseFont).height(); + initialized = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedScrollViewContainer, public: + +AdvancedScrollViewContainer::AdvancedScrollViewContainer(Profile* profile) + : contents_view_(new AdvancedContentsView(profile)), + scroll_view_(new ChromeViews::ScrollView) { + AddChildView(scroll_view_); + scroll_view_->SetContents(contents_view_); + SetBackground(new ListBackground()); +} + +AdvancedScrollViewContainer::~AdvancedScrollViewContainer() { +} + +//////////////////////////////////////////////////////////////////////////////// +// AdvancedScrollViewContainer, ChromeViews::View overrides: + +void AdvancedScrollViewContainer::Layout() { + CRect lb; + GetLocalBounds(&lb, false); + + gfx::Size border = gfx::NativeTheme::instance()->GetThemeBorderSize( + gfx::NativeTheme::LIST); + lb.DeflateRect(border.ToSIZE()); + scroll_view_->SetBounds(lb); + scroll_view_->Layout(); +} diff --git a/chrome/browser/views/options/advanced_contents_view.h b/chrome/browser/views/options/advanced_contents_view.h new file mode 100644 index 0000000..c3097e5 --- /dev/null +++ b/chrome/browser/views/options/advanced_contents_view.h @@ -0,0 +1,63 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_CONTENTS_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_CONTENTS_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" + +class AdvancedContentsView; +namespace ChromeViews { +class ScrollView; +} + +/////////////////////////////////////////////////////////////////////////////// +// AdvancedScrollViewContainer +// +// A View that contains a scroll view containing the Advanced options. + +class AdvancedScrollViewContainer : public ChromeViews::View { + public: + explicit AdvancedScrollViewContainer(Profile* profile); + virtual ~AdvancedScrollViewContainer(); + + // ChromeViews::View overrides: + virtual void Layout(); + + private: + // The contents of the advanced scroll view. + AdvancedContentsView* contents_view_; + + // The scroll view that contains the advanced options. + ChromeViews::ScrollView* scroll_view_; + + DISALLOW_EVIL_CONSTRUCTORS(AdvancedScrollViewContainer); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_CONTENTS_VIEW_H__ diff --git a/chrome/browser/views/options/advanced_page_view.cc b/chrome/browser/views/options/advanced_page_view.cc new file mode 100644 index 0000000..f47e5a6 --- /dev/null +++ b/chrome/browser/views/options/advanced_page_view.cc @@ -0,0 +1,210 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/advanced_page_view.h" + +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/options/advanced_contents_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/message_box_view.h" +#include "chrome/views/native_button.h" +#include "chrome/views/scroll_view.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +namespace { + +// A dialog box that asks the user to confirm resetting settings. +class ResetDefaultsConfirmBox : public ChromeViews::DialogDelegate { + public: + // This box is modal to |parent_hwnd|. + static void ShowConfirmBox(HWND parent_hwnd, AdvancedPageView* page_view) { + // When the window closes, it will delete itself. + new ResetDefaultsConfirmBox(parent_hwnd, page_view); + } + + protected: + // ChromeViews::DialogDelegate + virtual int GetDialogButtons() const { + return DIALOGBUTTON_OK | DIALOGBUTTON_CANCEL; + } + virtual std::wstring GetDialogButtonLabel(DialogButton button) const { + switch (button) { + case DIALOGBUTTON_OK: + return l10n_util::GetString(IDS_OPTIONS_RESET_OKLABEL); + case DIALOGBUTTON_CANCEL: + return l10n_util::GetString(IDS_OPTIONS_RESET_CANCELLABEL); + default: + break; + } + NOTREACHED(); + return std::wstring(); + } + virtual std::wstring GetWindowTitle() const { + return l10n_util::GetString(IDS_PRODUCT_NAME); + } + virtual bool Accept() { + advanced_page_view_->ResetToDefaults(); + return true; + } + // ChromeViews::WindowDelegate + virtual void WindowClosing() { delete this; } + virtual bool IsModal() const { return true; } + + private: + ResetDefaultsConfirmBox(HWND parent_hwnd, AdvancedPageView* page_view) + : advanced_page_view_(page_view) { + const int kDialogWidth = 400; + // Also deleted when the window closes. + MessageBoxView* message_box_view = new MessageBoxView( + MessageBoxView::kFlagHasMessage | MessageBoxView::kFlagHasOKButton, + l10n_util::GetString(IDS_OPTIONS_RESET_MESSAGE).c_str(), + std::wstring(), + kDialogWidth); + ChromeViews::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), + message_box_view, this)->Show(); + } + virtual ~ResetDefaultsConfirmBox() { } + + AdvancedPageView* advanced_page_view_; + + DISALLOW_EVIL_CONSTRUCTORS(ResetDefaultsConfirmBox); +}; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// AdvancedPageView, public: + +AdvancedPageView::AdvancedPageView(Profile* profile) + : advanced_scroll_view_(NULL), + reset_to_default_button_(NULL), + OptionsPageView(profile) { +} + +AdvancedPageView::~AdvancedPageView() { +} + +void AdvancedPageView::ResetToDefaults() { + // TODO(tc): It would be nice if we could generate this list automatically so + // changes to any of the options pages doesn't require updating this list + // manually. + PrefService* prefs = profile()->GetPrefs(); + const wchar_t* kUserPrefs[] = { + prefs::kAcceptLanguages, + prefs::kAlternateErrorPagesEnabled, + prefs::kBlockPopups, + prefs::kCookieBehavior, + prefs::kDefaultCharset, + prefs::kDnsPrefetchingEnabled, + prefs::kDownloadDefaultDirectory, + prefs::kDownloadExtensionsToOpen, + prefs::kHomePage, + prefs::kMixedContentFiltering, + prefs::kPromptForDownload, + prefs::kPasswordManagerEnabled, + prefs::kRestoreOnStartup, + prefs::kSafeBrowsingEnabled, + prefs::kShowHomeButton, + prefs::kSpellCheckDictionary, + prefs::kURLsToRestoreOnStartup, + prefs::kWebKitDefaultFixedFontSize, + prefs::kWebKitDefaultFontSize, + prefs::kWebKitFixedFontFamily, + prefs::kWebKitJavaEnabled, + prefs::kWebKitJavascriptEnabled, + prefs::kWebKitLoadsImagesAutomatically, + prefs::kWebKitPluginsEnabled, + prefs::kWebKitSansSerifFontFamily, + prefs::kWebKitSerifFontFamily, + }; + for (int i = 0; i < arraysize(kUserPrefs); ++i) + prefs->ClearPref(kUserPrefs[i]); + + PrefService* local_state = g_browser_process->local_state(); + // Note that we don't reset the kMetricsReportingEnabled preference here + // because the reset will reset it to the default setting specified in Chrome + // source, not the default setting selected by the user on the web page where + // they downloaded Chrome. This means that if the user ever resets their + // settings they'll either inadvertedly enable this logging or disable it. + // One is undesirable for them, one is undesirable for us. For now, we just + // don't reset it. + const wchar_t* kLocalStatePrefs[] = { + prefs::kApplicationLocale, + prefs::kOptionsWindowLastTabIndex, + }; + for (int i = 0; i < arraysize(kLocalStatePrefs); ++i) + local_state->ClearPref(kLocalStatePrefs[i]); +} + +/////////////////////////////////////////////////////////////////////////////// +// AdvancedPageView, ChromeViews::NativeButton::Listener implementation: + +void AdvancedPageView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == reset_to_default_button_) { + UserMetricsRecordAction(L"Options_ResetToDefaults", NULL); + ResetDefaultsConfirmBox::ShowConfirmBox(GetRootWindow(), this); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// AdvancedPageView, OptionsPageView implementation: + +void AdvancedPageView::InitControlLayout() { + reset_to_default_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_RESET)); + reset_to_default_button_->SetListener(this); + advanced_scroll_view_ = new AdvancedScrollViewContainer(profile()); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + layout->StartRow(1, single_column_view_set_id); + layout->AddView(advanced_scroll_view_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(reset_to_default_button_, 1, 1, + GridLayout::TRAILING, GridLayout::CENTER); +} diff --git a/chrome/browser/views/options/advanced_page_view.h b/chrome/browser/views/options/advanced_page_view.h new file mode 100644 index 0000000..1c78051 --- /dev/null +++ b/chrome/browser/views/options/advanced_page_view.h @@ -0,0 +1,67 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_PAGE_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/views/native_button.h" + +class AdvancedOptionsListModel; +class AdvancedScrollViewContainer; +class PrefService; + +/////////////////////////////////////////////////////////////////////////////// +// AdvancedPageView + +class AdvancedPageView : public OptionsPageView, + public ChromeViews::NativeButton::Listener { + public: + explicit AdvancedPageView(Profile* profile); + virtual ~AdvancedPageView(); + + // Resets all prefs to their default values. + void ResetToDefaults(); + + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + protected: + // OptionsPageView implementation: + virtual void InitControlLayout(); + + private: + // Controls for the Advanced page + AdvancedScrollViewContainer* advanced_scroll_view_; + ChromeViews::NativeButton* reset_to_default_button_; + + DISALLOW_EVIL_CONSTRUCTORS(AdvancedPageView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_ADVANCED_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/content_page_view.cc b/chrome/browser/views/options/content_page_view.cc new file mode 100644 index 0000000..6ed056f --- /dev/null +++ b/chrome/browser/views/options/content_page_view.cc @@ -0,0 +1,464 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windows.h> +#include <shlobj.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "chrome/browser/views/options/content_page_view.h" + +#include "base/file_util.h" +#include "base/gfx/native_theme.h" +#include "base/gfx/skia_utils.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/options/fonts_languages_window_view.h" +#include "chrome/browser/views/options/options_group_view.h" +#include "chrome/browser/views/password_manager_view.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/native_button.h" +#include "chrome/views/radio_button.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view_container.h" +#include "generated_resources.h" +#include "skia/include/SkBitmap.h" + +namespace { + +static const int kPopupBlockingRadioGroup = 1; +static const int kPasswordSavingRadioGroup = 2; +static const int kFileIconSize = 16; +static const int kFileIconVerticalSpacing = 3; +static const int kFileIconHorizontalSpacing = 3; +static const int kFileIconTextFieldSpacing = 3; +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// FileDisplayArea + +class FileDisplayArea : public ChromeViews::View { + public: + FileDisplayArea(); + virtual ~FileDisplayArea(); + + void SetFile(const std::wstring& file_path); + + // ChromeViews::View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + void Init(); + + ChromeViews::TextField* text_field_; + SkColor text_field_background_color_; + + gfx::Rect icon_bounds_; + + bool initialized_; + + static void InitClass(); + static SkBitmap default_folder_icon_; + + DISALLOW_EVIL_CONSTRUCTORS(FileDisplayArea); +}; + +// static +SkBitmap FileDisplayArea::default_folder_icon_; + +FileDisplayArea::FileDisplayArea() + : text_field_(new ChromeViews::TextField), + text_field_background_color_(0), + initialized_(false) { + InitClass(); +} + +FileDisplayArea::~FileDisplayArea() { +} + +void FileDisplayArea::SetFile(const std::wstring& file_path) { + text_field_->SetText(file_path); +} + +void FileDisplayArea::Paint(ChromeCanvas* canvas) { + HDC dc = canvas->beginPlatformPaint(); + RECT rect = { 0, 0, GetWidth(), GetHeight() }; + gfx::NativeTheme::instance()->PaintTextField( + dc, EP_EDITTEXT, ETS_READONLY, 0, &rect, + gfx::SkColorToCOLORREF(text_field_background_color_), true, true); + canvas->endPlatformPaint(); + canvas->DrawBitmapInt(default_folder_icon_, icon_bounds_.x(), + icon_bounds_.y()); +} + +void FileDisplayArea::Layout() { + icon_bounds_.SetRect(kFileIconHorizontalSpacing, kFileIconVerticalSpacing, + kFileIconSize, kFileIconSize); + CSize ps; + text_field_->GetPreferredSize(&ps); + text_field_->SetBounds(icon_bounds_.right() + kFileIconTextFieldSpacing, + (GetHeight() - ps.cy) / 2, + GetWidth() - icon_bounds_.right() - + kFileIconHorizontalSpacing - + kFileIconTextFieldSpacing, ps.cy); +} + +void FileDisplayArea::GetPreferredSize(CSize* out) { + DCHECK(out); + out->cx = kFileIconSize + 2 * kFileIconVerticalSpacing; + out->cy = kFileIconSize + 2 * kFileIconHorizontalSpacing; +} + +void FileDisplayArea::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (!initialized_ && is_add && GetViewContainer()) + Init(); +} + +void FileDisplayArea::Init() { + initialized_ = true; + AddChildView(text_field_); + text_field_background_color_ = + gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::TEXTFIELD, EP_EDITTEXT, ETS_READONLY, + TMT_FILLCOLOR, COLOR_3DFACE); + text_field_->SetReadOnly(true); + text_field_->RemoveBorder(); + text_field_->SetBackgroundColor(text_field_background_color_); +} + +void FileDisplayArea::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + default_folder_icon_ = *rb.GetBitmapNamed(IDR_FOLDER_CLOSED); + initialized = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ContentPageView, public: + +ContentPageView::ContentPageView(Profile* profile) + : download_location_group_(NULL), + download_default_download_location_display_(NULL), + download_browse_button_(NULL), + download_ask_for_save_location_checkbox_(NULL), + select_file_dialog_(SelectFileDialog::Create(this)), + passwords_group_(NULL), + passwords_asktosave_radio_(NULL), + passwords_neversave_radio_(NULL), + passwords_show_passwords_button_(NULL), + fonts_lang_group_(NULL), + fonts_and_languages_label_(NULL), + change_content_fonts_button_(NULL), + OptionsPageView(profile) { +} + +ContentPageView::~ContentPageView() { + select_file_dialog_->ListenerDestroyed(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ContentPageView, SelectFileDialog::Listener implementation: + +void ContentPageView::FileSelected(const std::wstring& path, void* params) { + UserMetricsRecordAction(L"Options_SetDownloadDirectory", + profile()->GetPrefs()); + default_download_location_.SetValue(path); + // We need to call this manually here since because we're setting the value + // through the pref member which avoids notifying the listener that set the + // value. + UpdateDownloadDirectoryDisplay(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentPageView, ChromeViews::NativeButton::Listener implementation: + +void ContentPageView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == download_browse_button_) { + const std::wstring dialog_title = + l10n_util::GetString(IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_TITLE); + select_file_dialog_->SelectFile(SelectFileDialog::SELECT_FOLDER, + dialog_title, L"", GetRootWindow(), NULL); + } else if (sender == download_ask_for_save_location_checkbox_) { + bool enabled = download_ask_for_save_location_checkbox_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_AskForSaveLocation_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_AskForSaveLocation_Disable", + profile()->GetPrefs()); + } + ask_for_save_location_.SetValue(enabled); + } else if (sender == passwords_asktosave_radio_ || + sender == passwords_neversave_radio_) { + bool enabled = passwords_asktosave_radio_->IsSelected(); + if (enabled) { + UserMetricsRecordAction(L"Options_PasswordManager_Enable", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_PasswordManager_Disable", + profile()->GetPrefs()); + } + ask_to_save_passwords_.SetValue(enabled); + } else if (sender == passwords_show_passwords_button_) { + UserMetricsRecordAction(L"Options_ShowPasswordManager", NULL); + PasswordManagerView::Show(profile()); + } else if (sender == change_content_fonts_button_) { + FontsLanguagesWindowView* flwv = new FontsLanguagesWindowView(profile()); + ChromeViews::Window* w = + ChromeViews::Window::CreateChromeWindow(GetRootWindow(), gfx::Rect(), + flwv, flwv); + w->Show(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ContentPageView, OptionsPageView implementation: + +bool ContentPageView::CanClose() const { + return !select_file_dialog_->IsRunning(GetRootWindow()); +} + +void ContentPageView::InitControlLayout() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(this); + layout->SetInsets(5, 5, 5, 5); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, single_column_view_set_id); + InitDownloadLocation(); + layout->AddView(download_location_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + InitPasswordSavingGroup(); + layout->AddView(passwords_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + InitFontsLangGroup(); + layout->AddView(fonts_lang_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Init member prefs so we can update the controls if prefs change. + default_download_location_.Init(prefs::kDownloadDefaultDirectory, + profile()->GetPrefs(), this); + ask_for_save_location_.Init(prefs::kPromptForDownload, + profile()->GetPrefs(), this); + ask_to_save_passwords_.Init(prefs::kPasswordManagerEnabled, + profile()->GetPrefs(), this); +} + +void ContentPageView::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kDownloadDefaultDirectory) + UpdateDownloadDirectoryDisplay(); + + if (!pref_name || *pref_name == prefs::kPromptForDownload) { + download_ask_for_save_location_checkbox_->SetIsSelected( + ask_for_save_location_.GetValue()); + } + if (!pref_name || *pref_name == prefs::kPasswordManagerEnabled) { + if (ask_to_save_passwords_.GetValue()) { + passwords_asktosave_radio_->SetIsSelected(true); + } else { + passwords_neversave_radio_->SetIsSelected(true); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentsPageView, ChromeViews::View overrides: + +void ContentPageView::Layout() { + // We need to Layout twice - once to get the width of the contents box... + View::Layout(); + download_ask_for_save_location_checkbox_->SetBounds( + 0, 0, download_location_group_->GetContentsWidth(), 0); + passwords_asktosave_radio_->SetBounds( + 0, 0, passwords_group_->GetContentsWidth(), 0); + passwords_neversave_radio_->SetBounds( + 0, 0, passwords_group_->GetContentsWidth(), 0); + // ... and twice to get the height of multi-line items correct. + View::Layout(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ContentPageView, private: + +void ContentPageView::InitDownloadLocation() { + download_default_download_location_display_ = new FileDisplayArea; + download_browse_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_DOWNLOADLOCATION_BROWSE_BUTTON)); + download_browse_button_->SetListener(this); + + download_ask_for_save_location_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_DOWNLOADLOCATION_ASKFORSAVELOCATION)); + download_ask_for_save_location_checkbox_->SetListener(this); + download_ask_for_save_location_checkbox_->SetMultiLine(true); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int double_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kUnrelatedControlHorizontalSpacing); + + layout->StartRow(0, double_column_view_set_id); + layout->AddView(download_default_download_location_display_, 1, 1, + GridLayout::FILL, GridLayout::CENTER); + layout->AddView(download_browse_button_); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + const int single_column_view_set_id = 1; + column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(download_ask_for_save_location_checkbox_); + + download_location_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_DOWNLOADLOCATION_GROUP_NAME), + std::wstring(), + true); +} + +void ContentPageView::InitPasswordSavingGroup() { + passwords_asktosave_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_PASSWORDS_ASKTOSAVE), + kPasswordSavingRadioGroup); + passwords_asktosave_radio_->SetListener(this); + passwords_asktosave_radio_->SetMultiLine(true); + passwords_neversave_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_PASSWORDS_NEVERSAVE), + kPasswordSavingRadioGroup); + passwords_neversave_radio_->SetListener(this); + passwords_neversave_radio_->SetMultiLine(true); + passwords_show_passwords_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_PASSWORDS_SHOWPASSWORDS)); + passwords_show_passwords_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int single_column_view_set_id = 1; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(passwords_asktosave_radio_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(passwords_neversave_radio_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(passwords_show_passwords_button_); + + passwords_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_PASSWORDS_GROUP_NAME), L"", + true); +} + +void ContentPageView::InitFontsLangGroup() { + fonts_and_languages_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_FONTSETTINGS_INFO)); + fonts_and_languages_label_->SetHorizontalAlignment( + ChromeViews::Label::ALIGN_LEFT); + change_content_fonts_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_FONTSETTINGS_CONFIGUREFONTS_BUTTON)); + change_content_fonts_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int single_column_view_set_id = 1; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(fonts_and_languages_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(change_content_fonts_button_); + + fonts_lang_group_ = new OptionsGroupView( + contents, + l10n_util::GetString(IDS_OPTIONS_FONTSANDLANGUAGES_GROUP_NAME), + L"", false); +} + +void ContentPageView::UpdateDownloadDirectoryDisplay() { + download_default_download_location_display_->SetFile( + default_download_location_.GetValue()); +} diff --git a/chrome/browser/views/options/content_page_view.h b/chrome/browser/views/options/content_page_view.h new file mode 100644 index 0000000..2230810 --- /dev/null +++ b/chrome/browser/views/options/content_page_view.h @@ -0,0 +1,114 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_CONTENT_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_CONTENT_PAGE_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/common/pref_member.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { +class CheckBox; +class RadioButton; +} +class FileDisplayArea; +class OptionsGroupView; +class PrefService; + +//////////////////////////////////////////////////////////////////////////////// +// ContentPageView + +class ContentPageView : public OptionsPageView, + public ChromeViews::NativeButton::Listener, + public SelectFileDialog::Listener { + public: + explicit ContentPageView(Profile* profile); + virtual ~ContentPageView(); + + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // SelectFileDialog::Listener implementation: + virtual void FileSelected(const std::wstring& path, void* params); + + // OptionsPageView implementation: + virtual bool CanClose() const; + + protected: + // OptionsPageView implementation: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + // ChromeViews::View overrides: + virtual void Layout(); + + private: + // Init all the dialog controls. + void InitDownloadLocation(); + void InitPasswordSavingGroup(); + void InitFontsLangGroup(); + + // Updates the directory displayed in the default download location view with + // the current value of the pref. + void UpdateDownloadDirectoryDisplay(); + + // Controls for the Download Location group. + OptionsGroupView* download_location_group_; + FileDisplayArea* download_default_download_location_display_; + ChromeViews::NativeButton* download_browse_button_; + ChromeViews::CheckBox* download_ask_for_save_location_checkbox_; + scoped_refptr<SelectFileDialog> select_file_dialog_; + + // Controls for the Password Saving group + OptionsGroupView* passwords_group_; + ChromeViews::RadioButton* passwords_asktosave_radio_; + ChromeViews::RadioButton* passwords_neversave_radio_; + ChromeViews::NativeButton* passwords_show_passwords_button_; + + // Controls for the Popup Blocking group. + OptionsGroupView* popups_group_; + ChromeViews::RadioButton* popups_show_minimized_radio_; + ChromeViews::RadioButton* popups_block_all_radio_; + + // Controls for the Fonts and Languages group. + OptionsGroupView* fonts_lang_group_; + ChromeViews::Label* fonts_and_languages_label_; + ChromeViews::NativeButton* change_content_fonts_button_; + + StringPrefMember default_download_location_; + BooleanPrefMember ask_for_save_location_; + BooleanPrefMember ask_to_save_passwords_; + + DISALLOW_EVIL_CONSTRUCTORS(ContentPageView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_CONTENT_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/cookies_view.cc b/chrome/browser/views/options/cookies_view.cc new file mode 100644 index 0000000..3b59198 --- /dev/null +++ b/chrome/browser/views/options/cookies_view.cc @@ -0,0 +1,791 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <algorithm> + +#include "chrome/browser/views/options/cookies_view.h" + +#include "base/string_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/profile.h" +#include "chrome/common/gfx/color_utils.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/time_format.h" +#include "chrome/common/win_util.h" +#include "chrome/views/border.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/text_field.h" +#include "chrome/views/table_view.h" +#include "generated_resources.h" +#include "net/base/cookie_monster.h" +#include "net/url_request/url_request_context.h" + +// static +ChromeViews::Window* CookiesView::instance_ = NULL; +static const int kCookieInfoViewBorderSize = 1; +static const int kCookieInfoViewInsetSize = 3; +static const int kSearchFilterDelayMs = 500; + +/////////////////////////////////////////////////////////////////////////////// +// CookiesTableModel + +class CookiesTableModel : public ChromeViews::TableModel { + public: + explicit CookiesTableModel(Profile* profile); + virtual ~CookiesTableModel() {} + + // Returns information about the Cookie at the specified index. + std::string GetDomainAt(int index); + CookieMonster::CanonicalCookie& GetCookieAt(int index); + + // Remove the specified cookies from the Cookie Monster and update the view. + void RemoveCookies(int start_index, int remove_count); + void RemoveAllShownCookies(); + + // ChromeViews::TableModel implementation: + virtual int RowCount(); + virtual std::wstring GetText(int row, int column_id); + virtual SkBitmap GetIcon(int row); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + + // Filter the cookies to only display matched results. + void UpdateSearchResults(const std::wstring& filter); + + private: + void LoadCookies(); + void DoFilter(); + + std::wstring filter_; + + // The profile from which this model sources cookies. + Profile* profile_; + + typedef CookieMonster::CookieList CookieList; + typedef std::vector<CookieMonster::CookieListPair*> CookiePtrList; + CookieList all_cookies_; + CookiePtrList shown_cookies_; + + ChromeViews::TableModelObserver* observer_; + + // Static resources for this object. + static SkBitmap cookie_icon_; + static void InitClass(); + + DISALLOW_EVIL_CONSTRUCTORS(CookiesTableModel); +}; + +// static +SkBitmap CookiesTableModel::cookie_icon_; + +/////////////////////////////////////////////////////////////////////////////// +// CookiesTableModel, public: + +CookiesTableModel::CookiesTableModel(Profile* profile) + : profile_(profile) { + InitClass(); + LoadCookies(); +} + +std::string CookiesTableModel::GetDomainAt(int index) { + DCHECK(index >= 0 && index < RowCount()); + return shown_cookies_.at(index)->first; +} + +CookieMonster::CanonicalCookie& CookiesTableModel::GetCookieAt(int index) { + DCHECK(index >= 0 && index < RowCount()); + return shown_cookies_.at(index)->second; +} + +void CookiesTableModel::RemoveCookies(int start_index, int remove_count) { + if (remove_count <= 0) { + NOTREACHED(); + return; + } + + CookieMonster* monster = profile_->GetRequestContext()->cookie_store(); + + // We need to update the searched results list, the full cookie list, + // and the view. We walk through the search results list (which is what + // is displayed) and map these back to the full cookie list. They should + // be in the same sort order, and always exist, so we can just walk once. + // We can't delete any entries from all_cookies_ without invaliding all of + // our pointers after it (which are in shown_cookies), so we go backwards. + CookiePtrList::iterator first = shown_cookies_.begin() + start_index; + CookiePtrList::iterator last = first + remove_count; + CookieList::iterator all_it = all_cookies_.end(); + while (last != first) { + --last; + --all_it; + // Seek to the corresponding entry in all_cookies_ + while (&*all_it != *last) --all_it; + // Delete the cookie from the monster + monster->DeleteCookie(all_it->first, all_it->second, true); + all_it = all_cookies_.erase(all_it); + } + + // By deleting entries from all_cookies, we just possibly moved stuff around + // and have thus invalidated all of our pointers, so rebuild shown_cookies. + // We could do this all better if there was a way to mark elements of + // all_cookies as dead instead of deleting, but this should be fine for now. + DoFilter(); + observer_->OnItemsRemoved(start_index, remove_count); +} + +void CookiesTableModel::RemoveAllShownCookies() { + RemoveCookies(0, RowCount()); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesTableModel, ChromeViews::TableModel implementation: + +int CookiesTableModel::RowCount() { + return static_cast<int>(shown_cookies_.size()); +} + +std::wstring CookiesTableModel::GetText(int row, int column_id) { + DCHECK(row >= 0 && row < RowCount()); + switch (column_id) { + case IDS_COOKIES_DOMAIN_COLUMN_HEADER: + { + // Domain cookies start with a trailing dot, but we will show this + // in the cookie details, show it without the dot in the list. + std::string& domain = shown_cookies_.at(row)->first; + if (!domain.empty() && domain[0] == '.') + return UTF8ToWide(domain.substr(1)); + return UTF8ToWide(domain); + } + break; + case IDS_COOKIES_NAME_COLUMN_HEADER: + return UTF8ToWide(shown_cookies_.at(row)->second.Name()); + break; + } + NOTREACHED(); + return L""; +} + +SkBitmap CookiesTableModel::GetIcon(int row) { + return cookie_icon_; +} + +void CookiesTableModel::SetObserver(ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesTableModel, private: + +// Returns true if |cookie| matches the specified filter, where "match" is +// defined as the cookie's domain, name and value contains filter text +// somewhere. +static bool ContainsFilterText(const std::string& domain, + const CookieMonster::CanonicalCookie& cookie, + const std::string& filter) { + return domain.find(filter) != std::string::npos || + cookie.Name().find(filter) != std::string::npos || + cookie.Value().find(filter) != std::string::npos; +} + +// Sort ignore the '.' prefix for domain cookies. +static bool CookieSorter(const CookieMonster::CookieListPair& cp1, + const CookieMonster::CookieListPair& cp2) { + bool is1domain = !cp1.first.empty() && cp1.first[0] == '.'; + bool is2domain = !cp2.first.empty() && cp2.first[0] == '.'; + + // They are both either domain or host cookies, sort them normally. + if (is1domain == is2domain) + return cp1.first < cp2.first; + + // One (but only one) is a domain cookie, skip the beginning '.'. + int comp = is1domain ? + cp1.first.compare(1, cp1.first.length() - 1, cp2.first) : + -cp2.first.compare(1, cp2.first.length() - 1, cp1.first); + + return comp < 0; +} + +void CookiesTableModel::LoadCookies() { + // mmargh mmargh mmargh! + CookieMonster* cookie_monster = + profile_->GetRequestContext()->cookie_store(); + all_cookies_ = cookie_monster->GetAllCookies(); + std::sort(all_cookies_.begin(), all_cookies_.end(), CookieSorter); + DoFilter(); +} + +void CookiesTableModel::DoFilter() { + std::string utf8_filter = WideToUTF8(filter_); + bool has_filter = !utf8_filter.empty(); + + shown_cookies_.clear(); + + CookieList::iterator iter = all_cookies_.begin(); + for (; iter != all_cookies_.end(); ++iter) { + if (!has_filter || + ContainsFilterText(iter->first, iter->second, utf8_filter)) { + shown_cookies_.push_back(&*iter); + } + } +} + +void CookiesTableModel::UpdateSearchResults(const std::wstring& filter) { + filter_ = filter; + DoFilter(); + observer_->OnModelChanged(); +} + +// static +void CookiesTableModel::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + cookie_icon_ = *rb.GetBitmapNamed(IDR_COOKIE_ICON); + initialized = true; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// CookiesTableView +// Overridden to handle Delete key presses + +class CookiesTableView : public ChromeViews::TableView { + public: + CookiesTableView(CookiesTableModel* cookies_model, + std::vector<ChromeViews::TableColumn> columns); + virtual ~CookiesTableView() {} + + // Removes the cookies associated with the selected rows in the TableView. + void RemoveSelectedCookies(); + + protected: + // ChromeViews::TableView implementation: + virtual void OnKeyDown(unsigned short virtual_keycode); + + private: + // Our model, as a CookiesTableModel. + CookiesTableModel* cookies_model_; + + DISALLOW_EVIL_CONSTRUCTORS(CookiesTableView); +}; + +CookiesTableView::CookiesTableView( + CookiesTableModel* cookies_model, + std::vector<ChromeViews::TableColumn> columns) + : ChromeViews::TableView(cookies_model, columns, + ChromeViews::ICON_AND_TEXT, false, true, true), + cookies_model_(cookies_model) { +} + +void CookiesTableView::RemoveSelectedCookies() { + // It's possible that we don't have anything selected. + if (SelectedRowCount() <= 0) + return; + + // Remove the selected cookies. + int selected_row = FirstSelectedRow(); + cookies_model_->RemoveCookies(selected_row, SelectedRowCount()); + // Keep an element selected + if (RowCount() > 0) + Select(std::min(RowCount() - 1, selected_row)); +} + +void CookiesTableView::OnKeyDown(unsigned short virtual_keycode) { + if (virtual_keycode == VK_DELETE) + RemoveSelectedCookies(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookieInfoView +// +// Responsible for displaying a tabular grid of Cookie information. +class CookieInfoView : public ChromeViews::View { + public: + CookieInfoView(); + virtual ~CookieInfoView(); + + // Update the display from the specified CookieNode. + void SetCookie(const std::string& domain, + const CookieMonster::CanonicalCookie& cookie_node); + + // Clears the cookie display to indicate that no or multiple cookies are + // selected. + void ClearCookieDisplay(); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Set up the view layout + void Init(); + + // Individual property labels + ChromeViews::Label* name_label_; + ChromeViews::TextField* name_value_field_; + ChromeViews::Label* content_label_; + ChromeViews::TextField* content_value_field_; + ChromeViews::Label* domain_label_; + ChromeViews::TextField* domain_value_field_; + ChromeViews::Label* path_label_; + ChromeViews::TextField* path_value_field_; + ChromeViews::Label* send_for_label_; + ChromeViews::TextField* send_for_value_field_; + ChromeViews::Label* created_label_; + ChromeViews::TextField* created_value_field_; + ChromeViews::Label* expires_label_; + ChromeViews::TextField* expires_value_field_; + + DISALLOW_EVIL_CONSTRUCTORS(CookieInfoView); +}; + +/////////////////////////////////////////////////////////////////////////////// +// CookieInfoView, public: + +CookieInfoView::CookieInfoView() + : name_label_(NULL), + name_value_field_(NULL), + content_label_(NULL), + content_value_field_(NULL), + domain_label_(NULL), + domain_value_field_(NULL), + path_label_(NULL), + path_value_field_(NULL), + send_for_label_(NULL), + send_for_value_field_(NULL), + created_label_(NULL), + created_value_field_(NULL), + expires_label_(NULL), + expires_value_field_(NULL) { +} + +CookieInfoView::~CookieInfoView() { +} + +void CookieInfoView::SetCookie(const std::string& domain, + const CookieMonster::CanonicalCookie& cookie) { + name_value_field_->SetText(UTF8ToWide(cookie.Name())); + content_value_field_->SetText(UTF8ToWide(cookie.Value())); + domain_value_field_->SetText(UTF8ToWide(domain)); + path_value_field_->SetText(UTF8ToWide(cookie.Path())); + created_value_field_->SetText( + TimeFormat::FriendlyDateAndTime(cookie.CreationDate())); + + if (cookie.DoesExpire()) { + expires_value_field_->SetText( + TimeFormat::FriendlyDateAndTime(cookie.ExpiryDate())); + } else { + // TODO(deanm) need a string that the average user can understand + // "When you quit or restart your browser" ? + expires_value_field_->SetText( + l10n_util::GetString(IDS_COOKIES_COOKIE_EXPIRES_SESSION)); + } + + std::wstring sendfor_text; + if (cookie.IsSecure()) { + sendfor_text = l10n_util::GetString(IDS_COOKIES_COOKIE_SENDFOR_SECURE); + } else { + sendfor_text = l10n_util::GetString(IDS_COOKIES_COOKIE_SENDFOR_ANY); + } + send_for_value_field_->SetText(sendfor_text); +} + +void CookieInfoView::ClearCookieDisplay() { + std::wstring no_cookie_string = + l10n_util::GetString(IDS_COOKIES_COOKIE_NONESELECTED); + name_value_field_->SetText(no_cookie_string); + name_value_field_->SetEnabled(false); + content_value_field_->SetText(no_cookie_string); + content_value_field_->SetEnabled(false); + domain_value_field_->SetText(no_cookie_string); + domain_value_field_->SetEnabled(false); + path_value_field_->SetText(no_cookie_string); + path_value_field_->SetEnabled(false); + send_for_value_field_->SetText(no_cookie_string); + send_for_value_field_->SetEnabled(false); + created_value_field_->SetText(no_cookie_string); + created_value_field_->SetEnabled(false); + expires_value_field_->SetText(no_cookie_string); + expires_value_field_->SetEnabled(false); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookieInfoView, ChromeViews::View overrides: + +void CookieInfoView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (is_add && child == this) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookieInfoView, private: + +void CookieInfoView::Init() { + SkColor border_color = color_utils::GetSysSkColor(COLOR_3DSHADOW); + ChromeViews::Border* border = ChromeViews::Border::CreateSolidBorder( + kCookieInfoViewBorderSize, border_color); + SetBorder(border); + + name_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_NAME_LABEL)); + name_value_field_ = new ChromeViews::TextField; + content_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_CONTENT_LABEL)); + content_value_field_ = new ChromeViews::TextField; + domain_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_DOMAIN_LABEL)); + domain_value_field_ = new ChromeViews::TextField; + path_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_PATH_LABEL)); + path_value_field_ = new ChromeViews::TextField; + send_for_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_SENDFOR_LABEL)); + send_for_value_field_ = new ChromeViews::TextField; + created_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_CREATED_LABEL)); + created_value_field_ = new ChromeViews::TextField; + expires_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_COOKIE_EXPIRES_LABEL)); + expires_value_field_ = new ChromeViews::TextField; + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(this); + layout->SetInsets(kCookieInfoViewInsetSize, + kCookieInfoViewInsetSize, + kCookieInfoViewInsetSize, + kCookieInfoViewInsetSize); + SetLayoutManager(layout); + + int three_column_layout_id = 0; + ColumnSet* column_set = layout->AddColumnSet(three_column_layout_id); + column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, three_column_layout_id); + layout->AddView(name_label_); + layout->AddView(name_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(content_label_); + layout->AddView(content_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(domain_label_); + layout->AddView(domain_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(path_label_); + layout->AddView(path_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(send_for_label_); + layout->AddView(send_for_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(created_label_); + layout->AddView(created_value_field_); + layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing); + layout->StartRow(0, three_column_layout_id); + layout->AddView(expires_label_); + layout->AddView(expires_value_field_); + + // Color these borderless text areas the same as the containing dialog. + SkColor text_area_background = color_utils::GetSysSkColor(COLOR_3DFACE); + // Now that the TextFields are in the view hierarchy, we can initialize them. + name_value_field_->SetReadOnly(true); + name_value_field_->RemoveBorder(); + name_value_field_->SetBackgroundColor(text_area_background); + content_value_field_->SetReadOnly(true); + content_value_field_->RemoveBorder(); + content_value_field_->SetBackgroundColor(text_area_background); + domain_value_field_->SetReadOnly(true); + domain_value_field_->RemoveBorder(); + domain_value_field_->SetBackgroundColor(text_area_background); + path_value_field_->SetReadOnly(true); + path_value_field_->RemoveBorder(); + path_value_field_->SetBackgroundColor(text_area_background); + send_for_value_field_->SetReadOnly(true); + send_for_value_field_->RemoveBorder(); + send_for_value_field_->SetBackgroundColor(text_area_background); + created_value_field_->SetReadOnly(true); + created_value_field_->RemoveBorder(); + created_value_field_->SetBackgroundColor(text_area_background); + expires_value_field_->SetReadOnly(true); + expires_value_field_->RemoveBorder(); + expires_value_field_->SetBackgroundColor(text_area_background); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, public: + +// static +void CookiesView::ShowCookiesWindow(Profile* profile) { + if (!instance_) { + CookiesView* cookies_view = new CookiesView(profile); + instance_ = ChromeViews::Window::CreateChromeWindow( + NULL, gfx::Rect(), cookies_view, cookies_view); + } + if (!instance_->IsVisible()) { + instance_->Show(); + } else { + instance_->Activate(); + } +} + +CookiesView::~CookiesView() { + cookies_table_->SetModel(NULL); +} + +void CookiesView::UpdateSearchResults() { + cookies_table_model_->UpdateSearchResults(search_field_->GetText()); + remove_all_button_->SetEnabled(cookies_table_model_->RowCount() > 0); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, ChromeViews::NativeButton::listener implementation: + +void CookiesView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == remove_button_) { + cookies_table_->RemoveSelectedCookies(); + } else if (sender == remove_all_button_) { + // Delete all the Cookies shown. + cookies_table_model_->RemoveAllShownCookies(); + UpdateForEmptyState(); + } else if (sender == clear_search_button_) { + ResetSearchQuery(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, ChromeViews::TableViewObserver implementation: +void CookiesView::OnSelectionChanged() { + int selected_row_count = cookies_table_->SelectedRowCount(); + if (selected_row_count == 1) { + int selected_index = cookies_table_->FirstSelectedRow(); + if (selected_index >= 0 && + selected_index < cookies_table_model_->RowCount()) { + info_view_->SetCookie(cookies_table_model_->GetDomainAt(selected_index), + cookies_table_model_->GetCookieAt(selected_index)); + } + } else { + info_view_->ClearCookieDisplay(); + } + remove_button_->SetEnabled(selected_row_count != 0); + if (cookies_table_->RowCount() == 0) + UpdateForEmptyState(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, ChromeViews::TextField::Controller implementation: + +void CookiesView::ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents) { + search_update_factory_.RevokeAll(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + search_update_factory_.NewRunnableMethod( + &CookiesView::UpdateSearchResults), kSearchFilterDelayMs); +} + +void CookiesView::HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags) { + switch (key) { + case VK_ESCAPE: + ResetSearchQuery(); + break; + case VK_RETURN: + search_update_factory_.RevokeAll(); + UpdateSearchResults(); + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, ChromeViews::DialogDelegate implementation: + +std::wstring CookiesView::GetWindowTitle() const { + return l10n_util::GetString(IDS_COOKIES_WINDOW_TITLE); +} + +void CookiesView::WindowClosing() { + instance_ = NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, ChromeViews::View overrides: + +void CookiesView::Layout() { + // Lay out the Remove/Remove All buttons in the parent view. + CSize ps; + remove_button_->GetPreferredSize(&ps); + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + int y_buttons = parent_bounds.bottom - ps.cy - kButtonVEdgeMargin; + + remove_button_->SetBounds(kPanelHorizMargin, y_buttons, ps.cx, ps.cy); + + remove_all_button_->GetPreferredSize(&ps); + int remove_all_x = remove_button_->GetX() + remove_button_->GetWidth() + + kRelatedControlHorizontalSpacing; + remove_all_button_->SetBounds(remove_all_x, y_buttons, ps.cx, ps.cy); + + // Lay out this View + View::Layout(); +} + +void CookiesView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_COOKIES_DIALOG_WIDTH_CHARS, + IDS_COOKIES_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void CookiesView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (is_add && child == this) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CookiesView, private: + +CookiesView::CookiesView(Profile* profile) + : search_label_(NULL), + search_field_(NULL), + clear_search_button_(NULL), + description_label_(NULL), + cookies_table_(NULL), + info_view_(NULL), + remove_button_(NULL), + remove_all_button_(NULL), + profile_(profile), + search_update_factory_(this) { +} + +void CookiesView::Init() { + search_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_SEARCH_LABEL)); + search_field_ = new ChromeViews::TextField; + search_field_->SetController(this); + clear_search_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_COOKIES_CLEAR_SEARCH_LABEL)); + clear_search_button_->SetListener(this); + description_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_COOKIES_INFO_LABEL)); + description_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + + cookies_table_model_.reset(new CookiesTableModel(profile_)); + info_view_ = new CookieInfoView; + std::vector<ChromeViews::TableColumn> columns; + columns.push_back(ChromeViews::TableColumn(IDS_COOKIES_DOMAIN_COLUMN_HEADER, + ChromeViews::TableColumn::LEFT, + 200, 0.5f)); + columns.push_back(ChromeViews::TableColumn(IDS_COOKIES_NAME_COLUMN_HEADER, + ChromeViews::TableColumn::LEFT, + 150, 0.5f)); + cookies_table_ = new CookiesTableView(cookies_table_model_.get(), columns); + cookies_table_->SetObserver(this); + remove_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_COOKIES_REMOVE_LABEL)); + remove_button_->SetListener(this); + remove_all_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_COOKIES_REMOVE_ALL_LABEL)); + remove_all_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int five_column_layout_id = 0; + ColumnSet* column_set = layout->AddColumnSet(five_column_layout_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + + const int single_column_layout_id = 1; + column_set = layout->AddColumnSet(single_column_layout_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, five_column_layout_id); + layout->AddView(search_label_); + layout->AddView(search_field_); + layout->AddView(clear_search_button_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + layout->StartRow(0, single_column_layout_id); + layout->AddView(description_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(1, single_column_layout_id); + layout->AddView(cookies_table_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_layout_id); + layout->AddView(info_view_); + + // Add the Remove/Remove All buttons to the ClientView + View* parent = GetParent(); + parent->AddChildView(remove_button_); + parent->AddChildView(remove_all_button_); + + if (cookies_table_->RowCount() > 0) { + cookies_table_->Select(0); + } else { + UpdateForEmptyState(); + } +} + +void CookiesView::ResetSearchQuery() { + search_field_->SetText(EmptyWString()); + UpdateSearchResults(); +} + +void CookiesView::UpdateForEmptyState() { + info_view_->ClearCookieDisplay(); + remove_button_->SetEnabled(false); + remove_all_button_->SetEnabled(false); +} diff --git a/chrome/browser/views/options/cookies_view.h b/chrome/browser/views/options/cookies_view.h new file mode 100644 index 0000000..9547acc --- /dev/null +++ b/chrome/browser/views/options/cookies_view.h @@ -0,0 +1,136 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_COOKIES_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_COOKIES_VIEW_H__ + +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" + +namespace ChromeViews { +class Label; +} +class CookieInfoView; +class CookiesTableModel; +class CookiesTableView; +class Profile; +class Timer; + +class CookiesView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::NativeButton::Listener, + public ChromeViews::TableViewObserver, + public ChromeViews::TextField::Controller { + public: + // Show the Cookies Window, creating one if necessary. + static void ShowCookiesWindow(Profile* profile); + + virtual ~CookiesView(); + + // Updates the display to show only the search results. + void UpdateSearchResults(); + + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ChromeViews::TableViewObserver implementation: + virtual void OnSelectionChanged(); + + // ChromeViews::TextField::Controller implementation: + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags); + + // ChromeViews::WindowDelegate implementation: + virtual int GetDialogButtons() const { return DIALOGBUTTON_CANCEL; } + virtual ChromeViews::View* GetInitiallyFocusedView() const { + return search_field_; + } + virtual bool CanResize() const { return true; } + virtual std::wstring GetWindowTitle() const; + virtual void WindowClosing(); + + // ChromeViews::View overrides: + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + // Use the static factory method to show. + explicit CookiesView(Profile* profile); + + // Initialize the dialog contents and layout. + void Init(); + + // Resets the display to what it would be if there were no search query. + void ResetSearchQuery(); + + // Update the UI when there are no cookies. + void UpdateForEmptyState(); + + // Assorted dialog controls + ChromeViews::Label* search_label_; + ChromeViews::TextField* search_field_; + ChromeViews::NativeButton* clear_search_button_; + ChromeViews::Label* description_label_; + CookiesTableView* cookies_table_; + CookieInfoView* info_view_; + ChromeViews::NativeButton* remove_button_; + ChromeViews::NativeButton* remove_all_button_; + + // The Cookies Table model + scoped_ptr<CookiesTableModel> cookies_table_model_; + scoped_ptr<CookiesTableModel> search_table_model_; + + // The Profile for which Cookies are displayed + Profile* profile_; + + // A factory to construct Runnable Methods so that we can be called back to + // re-evaluate the model after the search query string changes. + ScopedRunnableMethodFactory<CookiesView> search_update_factory_; + + // Our containing window. If this is non-NULL there is a visible Cookies + // window somewhere. + static ChromeViews::Window* instance_; + + DISALLOW_EVIL_CONSTRUCTORS(CookiesView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_GENERAL_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/fonts_languages_window_view.cc b/chrome/browser/views/options/fonts_languages_window_view.cc new file mode 100644 index 0000000..7b7788d --- /dev/null +++ b/chrome/browser/views/options/fonts_languages_window_view.cc @@ -0,0 +1,115 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/fonts_languages_window_view.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/options/fonts_page_view.h" +#include "chrome/browser/views/options/languages_page_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +// static +static FontsLanguagesWindowView* instance_ = NULL; +static const int kDialogPadding = 7; + +/////////////////////////////////////////////////////////////////////////////// +// FontsLanguagesWindowView, public: + +FontsLanguagesWindowView::FontsLanguagesWindowView(Profile* profile) + // Always show preferences for the original profile. Most state when off + // the record comes from the original profile, but we explicitly use + // the original profile to avoid potential problems. + : profile_(profile->GetOriginalProfile()), + fonts_page_(NULL), + languages_page_(NULL) { +} + +FontsLanguagesWindowView::~FontsLanguagesWindowView() { +} + +/////////////////////////////////////////////////////////////////////////////// +// FontsLanguagesWindowView, ChromeViews::DialogDelegate implementation: + +std::wstring FontsLanguagesWindowView::GetWindowTitle() const { + return l10n_util::GetStringF(IDS_FONT_LANGUAGE_SETTING_WINDOWS_TITLE, + l10n_util::GetString(IDS_PRODUCT_NAME)); +} + +bool FontsLanguagesWindowView::Accept() { + fonts_page_->SaveChanges(); + languages_page_->SaveChanges(); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// FontsLanguagesWindowView, ChromeViews::View overrides: + +void FontsLanguagesWindowView::Layout() { + tabs_->SetBounds(kDialogPadding, kDialogPadding, + GetWidth() - (2 * kDialogPadding), + GetHeight() - (2 * kDialogPadding)); +} + +void FontsLanguagesWindowView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_FONTSLANG_DIALOG_WIDTH_CHARS, + IDS_FONTSLANG_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void FontsLanguagesWindowView::ViewHierarchyChanged( + bool is_add, ChromeViews::View* parent, ChromeViews::View* child) { + // Can't init before we're inserted into a ViewContainer, because we require + // a HWND to parent native child controls to. + if (is_add && child == this) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// FontsLanguagesWindowView, private: + +void FontsLanguagesWindowView::Init() { + tabs_ = new ChromeViews::TabbedPane; + AddChildView(tabs_); + + fonts_page_ = new FontsPageView(profile_); + tabs_->AddTabAtIndex(0, l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_TAB_TITLE), fonts_page_, true); + + languages_page_ = new LanguagesPageView(profile_); + tabs_->AddTabAtIndex(1, l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_TAB_TITLE), languages_page_, true); +} diff --git a/chrome/browser/views/options/fonts_languages_window_view.h b/chrome/browser/views/options/fonts_languages_window_view.h new file mode 100644 index 0000000..bdae5ea --- /dev/null +++ b/chrome/browser/views/options/fonts_languages_window_view.h @@ -0,0 +1,98 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_FONTS_LANGUAGE_WINDOW_H__ +#define CHROME_BROWSER_FONTS_LANGUAGE_WINDOW_H__ + +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/tabbed_pane.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" + +class Profile; +class FontsPageView; +class LanguagesPageView; + +/////////////////////////////////////////////////////////////////////////////// +// FontsLanguagesWindowView +// +// The contents of the "Fonts and Languages Preferences" dialog window. +// +class FontsLanguagesWindowView : public ChromeViews::View, + public ChromeViews::DialogDelegate { + public: + explicit FontsLanguagesWindowView(Profile* profile); + virtual ~FontsLanguagesWindowView(); + + ChromeViews::Window* container() const { return container_; } + void set_container(ChromeViews::Window* container) { + container_ = container; + } + + // ChromeViews::DialogDelegate implementation: + virtual bool Accept(); + virtual std::wstring GetWindowTitle() const; + + // ChromeViews::WindowDelegate Methods: + virtual bool IsModal() const { return true; } + + // ChromeViews::View overrides: + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + private: + // Init the assorted Tabbed pages + void Init(); + + // The Tab view that contains all of the options pages. + ChromeViews::TabbedPane* tabs_; + + // The Options dialog window. + ChromeViews::Window* container_; + + // Fonts Page View handle remembered so that prefs is updated only when + // OK is pressed. + FontsPageView* fonts_page_; + + // Languages Page View handle remembered so that prefs is updated only when + // OK is pressed. + LanguagesPageView* languages_page_; + + // The Profile associated with these options. + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(FontsLanguagesWindowView); +}; + +#endif // #ifndef CHROME_BROWSER_FONTS_LANGUAGE_WINDOW_H__ diff --git a/chrome/browser/views/options/fonts_page_view.cc b/chrome/browser/views/options/fonts_page_view.cc new file mode 100644 index 0000000..0241cc0 --- /dev/null +++ b/chrome/browser/views/options/fonts_page_view.cc @@ -0,0 +1,509 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <windows.h> +#include <shlobj.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "chrome/browser/views/options/fonts_page_view.h" + +#include "base/file_util.h" +#include "base/gfx/native_theme.h" +#include "base/gfx/skia_utils.h" +#include "base/string_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/password_manager_view.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/native_button.h" +#include "chrome/views/radio_button.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view_container.h" +#include "generated_resources.h" +#include "skia/include/SkBitmap.h" + +class DefaultEncodingComboboxModel : public ChromeViews::ComboBox::Model { + public: + DefaultEncodingComboboxModel() { + canonical_encoding_names_length_ = + CharacterEncoding::GetSupportCanonicalEncodingCount(); + } + + virtual ~DefaultEncodingComboboxModel() {} + + // Overridden from ChromeViews::Combobox::Model. + virtual int GetItemCount(ChromeViews::ComboBox* source) { + return canonical_encoding_names_length_; + } + + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index) { + DCHECK(index >= 0 && canonical_encoding_names_length_ > index); + return CharacterEncoding::GetCanonicalEncodingDisplayNameByIndex(index); + } + + std::wstring GetEncodingCharsetByIndex(int index) { + DCHECK(index >= 0 && canonical_encoding_names_length_ > index); + return CharacterEncoding::GetCanonicalEncodingNameByIndex(index); + } + + int GetSelectedEncodingIndex(Profile* profile) { + StringPrefMember current_encoding_string; + current_encoding_string.Init(prefs::kDefaultCharset, + profile->GetPrefs(), + NULL); + const std::wstring current_encoding = current_encoding_string.GetValue(); + for (int i = 0; i < canonical_encoding_names_length_; i++) { + if (CharacterEncoding::GetCanonicalEncodingNameByIndex(i) == + current_encoding) + return i; + } + + return 0; + } + + private: + int canonical_encoding_names_length_; + DISALLOW_EVIL_CONSTRUCTORS(DefaultEncodingComboboxModel); +}; + +//////////////////////////////////////////////////////////////////////////////// +// FontDisplayView + +class FontDisplayView : public ChromeViews::View { + public: + FontDisplayView(); + virtual ~FontDisplayView(); + + void SetFontType(const std::wstring& font_name, + int font_size); + + void SetFontType(ChromeFont font); + + std::wstring font_name() { return font_name_; } + int font_size() { return font_size_; } + + // ChromeViews::View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + private: + ChromeViews::Label* font_text_label_; + std::wstring font_name_; + int font_size_; + std::wstring font_text_label_string_; + + static const int kFontDisplayMaxWidthChars = 50; + static const int kFontDisplayMaxHeightChars = 1; + static const int kFontDisplayLabelPadding = 5; + + DISALLOW_EVIL_CONSTRUCTORS(FontDisplayView); +}; + +FontDisplayView::FontDisplayView() + : font_text_label_(new ChromeViews::Label) { + AddChildView(font_text_label_); +} + +FontDisplayView::~FontDisplayView() { +} + +void FontDisplayView::SetFontType(ChromeFont font) { + if (font.FontName().empty()) + return; + + font_name_ = font.FontName(); + font_size_ = font.FontSize(); + + // Append the font type and size here. + std::wstring displayed_text = font_name_; + displayed_text += L", "; + displayed_text += UTF8ToWide(StringPrintf("%d", font_size_)); + displayed_text += L"pt"; + + // Set Label. + font_text_label_->SetText(displayed_text); + font_text_label_->SetFont(font); +} + +void FontDisplayView::SetFontType(const std::wstring& font_name, + int font_size) { + if (font_name.empty()) + return; + + font_name_ = font_name; + font_size_ = font_size; + std::wstring displayed_text = font_name_; + + // Append the font type and size. + displayed_text += L", "; + displayed_text += UTF8ToWide(::StringPrintf("%d", font_size_)); + displayed_text += L"pt"; + ChromeFont font = ChromeFont::CreateFont(font_name, font_size); + font_text_label_->SetFont(font); + font_text_label_->SetText(displayed_text); +} + +void FontDisplayView::Paint(ChromeCanvas* canvas) { + HDC dc = canvas->beginPlatformPaint(); + RECT rect = { 0, 0, GetWidth(), GetHeight() }; + gfx::NativeTheme::instance()->PaintTextField( + dc, EP_BACKGROUND, EBS_NORMAL, 0, &rect, ::GetSysColor(COLOR_3DFACE), + true, true); + canvas->endPlatformPaint(); +} + +void FontDisplayView::Layout() { + font_text_label_->SetBounds(0, 0, GetWidth(), GetHeight()); +} + +void FontDisplayView::GetPreferredSize(CSize* out) { + DCHECK(out); + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + out->cx = font.ave_char_width() * kFontDisplayMaxWidthChars; + out->cy = font.height() * kFontDisplayMaxHeightChars + + 2 * kFontDisplayLabelPadding; +} + +void EmbellishTitle(ChromeViews::Label* title_label) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont title_font = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(0, ChromeFont::BOLD); + title_label->SetFont(title_font); + SkColor title_color = + gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::BUTTON, BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, + COLOR_WINDOWTEXT); + title_label->SetColor(title_color); +} + +FontsPageView::FontsPageView(Profile* profile) + : select_font_dialog_(SelectFontDialog::Create(this)), + fonts_group_title_(NULL), + encoding_group_title_(NULL), + fixed_width_font_change_page_button_(NULL), + serif_font_change_page_button_(NULL), + sans_serif_font_change_page_button_(NULL), + fixed_width_font_label_(NULL), + serif_font_label_(NULL), + sans_serif_font_label_(NULL), + default_encoding_combobox_(NULL), + serif_button_pressed_(false), + sans_serif_button_pressed_(false), + fixed_width_button_pressed_(false), + encoding_dropdown_clicked_(false), + font_type_being_changed_(NONE), + OptionsPageView(profile), + font_changed_(false), + default_encoding_changed_(false) { + serif_name_.Init(prefs::kWebKitSerifFontFamily, profile->GetPrefs(), NULL); + serif_size_.Init(prefs::kWebKitDefaultFontSize, profile->GetPrefs(), NULL); + + sans_serif_name_.Init(prefs::kWebKitSansSerifFontFamily, profile->GetPrefs(), + NULL); + sans_serif_size_.Init(prefs::kWebKitDefaultFontSize, profile->GetPrefs(), + NULL); + + fixed_width_name_.Init(prefs::kWebKitFixedFontFamily, profile->GetPrefs(), + NULL); + fixed_width_size_.Init(prefs::kWebKitDefaultFixedFontSize, + profile->GetPrefs(), NULL); + + default_encoding_.Init(prefs::kDefaultCharset, profile->GetPrefs(), NULL); +} + +FontsPageView::~FontsPageView() { +} + +void FontsPageView::ButtonPressed(ChromeViews::NativeButton* sender) { + HWND owning_hwnd = GetAncestor(GetViewContainer()->GetHWND(), GA_ROOT); + std::wstring font_name; + int font_size = 0; + if (sender == serif_font_change_page_button_) { + font_type_being_changed_ = SERIF; + font_name = serif_font_display_view_->font_name(); + font_size = serif_font_display_view_->font_size(); + } else if (sender == sans_serif_font_change_page_button_) { + font_type_being_changed_ = SANS_SERIF; + font_name = sans_serif_font_display_view_->font_name(); + font_size = sans_serif_font_display_view_->font_size(); + } else if (sender == fixed_width_font_change_page_button_) { + font_type_being_changed_ = FIXED_WIDTH; + font_name = fixed_width_font_display_view_->font_name(); + font_size = fixed_width_font_display_view_->font_size(); + } else { + NOTREACHED(); + return; + } + + select_font_dialog_->SelectFont(owning_hwnd, NULL, font_name, font_size); +} + +void FontsPageView::ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, int new_index) { + if (combo_box == default_encoding_combobox_) { + if (prev_index != new_index) { // Default-Encoding has been changed. + encoding_dropdown_clicked_ = true; + default_encoding_selected_ = default_encoding_combobox_model_-> + GetEncodingCharsetByIndex(new_index); + default_encoding_changed_ = true; + } + } +} + +void FontsPageView::FontSelected(const ChromeFont& font, void* params) { + if (ChromeFont(font).FontName().empty()) + return; + int font_size = ChromeFont(font).FontSize(); + if (font_type_being_changed_ == SERIF) { + serif_font_display_view_->SetFontType(font); + sans_serif_font_display_view_->SetFontType( + sans_serif_font_display_view_->font_name(), font_size); + } else if (font_type_being_changed_ == SANS_SERIF) { + sans_serif_font_display_view_->SetFontType(font); + serif_font_display_view_->SetFontType( + serif_font_display_view_->font_name(), font_size); + } else if (font_type_being_changed_ == FIXED_WIDTH) { + fixed_width_font_display_view_->SetFontType(font); + } + font_changed_ = true; +} + +void FontsPageView::SaveChanges() { + // Set Fonts. + if (font_changed_) { + serif_name_.SetValue(serif_font_display_view_->font_name()); + serif_size_.SetValue(serif_font_display_view_->font_size()); + sans_serif_name_.SetValue(sans_serif_font_display_view_->font_name()); + sans_serif_size_.SetValue(sans_serif_font_display_view_->font_size()); + fixed_width_name_.SetValue(fixed_width_font_display_view_->font_name()); + fixed_width_size_.SetValue(fixed_width_font_display_view_->font_size()); + } + // Set Encoding. + if (default_encoding_changed_) + default_encoding_.SetValue(default_encoding_selected_); +} + +void FontsPageView::InitControlLayout() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + + // Fonts group. + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 1, + GridLayout::USE_PREF, 0, 0); + fonts_group_title_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SUB_DIALOG_FONT_TITLE)); + EmbellishTitle(fonts_group_title_); + fonts_group_title_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(fonts_group_title_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + InitFontLayout(); + layout->AddView(fonts_contents_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + // Encoding group. + encoding_group_title_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SUB_DIALOG_ENCODING_TITLE)); + EmbellishTitle(encoding_group_title_); + encoding_group_title_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(encoding_group_title_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + InitEncodingLayout(); + layout->AddView(encoding_contents_); +} + +void FontsPageView::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kWebKitFixedFontFamily) { + fixed_width_font_display_view_->SetFontType( + fixed_width_name_.GetValue(), + fixed_width_size_.GetValue()); + } + if (!pref_name || *pref_name == prefs::kWebKitSerifFontFamily) { + serif_font_display_view_->SetFontType( + serif_name_.GetValue(), + serif_size_.GetValue()); + } + if (!pref_name || *pref_name == prefs::kWebKitSansSerifFontFamily) { + sans_serif_font_display_view_->SetFontType( + sans_serif_name_.GetValue(), + sans_serif_size_.GetValue()); + } +} + +void FontsPageView::InitFontLayout() { + // Fixed width. + fixed_width_font_display_view_ = new FontDisplayView; + fixed_width_font_change_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_BUTTON_LABEL)); + fixed_width_font_change_page_button_->SetListener(this); + + fixed_width_font_label_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_FIXED_WIDTH_LABEL)); + fixed_width_font_label_->SetHorizontalAlignment(ChromeViews::Label:: + ALIGN_LEFT); + fixed_width_font_label_->SetMultiLine(true); + + // Serif font. + serif_font_display_view_ = new FontDisplayView; + serif_font_change_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_BUTTON_LABEL)); + serif_font_change_page_button_->SetListener(this); + + serif_font_label_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SERIF_LABEL)); + serif_font_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + serif_font_label_->SetMultiLine(true); + + // Sans Serif font. + sans_serif_font_display_view_ = new FontDisplayView; + sans_serif_font_change_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_BUTTON_LABEL)); + sans_serif_font_change_page_button_->SetListener(this); + + sans_serif_font_label_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_SELECTOR_SANS_SERIF_LABEL)); + sans_serif_font_label_->SetHorizontalAlignment(ChromeViews::Label:: + ALIGN_LEFT); + sans_serif_font_label_->SetMultiLine(true); + + // Now add the views. + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + fonts_contents_ = new ChromeViews::View; + GridLayout* layout = new GridLayout(fonts_contents_); + fonts_contents_->SetLayoutManager(layout); + + const int triple_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(triple_column_view_set_id); + + int label_width = + _wtoi(l10n_util::GetString(IDS_FONTSLANG_LABEL_WIDTH).c_str()) * + ChromeFont().ave_char_width(); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::FIXED, label_width, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + // Serif font controls. + layout->StartRow(0, triple_column_view_set_id); + layout->AddView(serif_font_label_); + layout->AddView(serif_font_display_view_, 1, 1, + GridLayout::FILL, GridLayout::CENTER); + layout->AddView(serif_font_change_page_button_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Sans serif font controls. + layout->StartRow(0, triple_column_view_set_id); + layout->AddView(sans_serif_font_label_); + layout->AddView(sans_serif_font_display_view_, 1, 1, + GridLayout::FILL, GridLayout::CENTER); + layout->AddView(sans_serif_font_change_page_button_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Fixed-width font controls. + layout->StartRow(0, triple_column_view_set_id); + layout->AddView(fixed_width_font_label_); + layout->AddView(fixed_width_font_display_view_, 1, 1, + GridLayout::FILL, GridLayout::CENTER); + layout->AddView(fixed_width_font_change_page_button_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); +} + +void FontsPageView::InitEncodingLayout() { + default_encoding_combobox_label_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_FONT_DEFAULT_ENCODING_SELECTOR_LABEL)); + default_encoding_combobox_model_.reset(new DefaultEncodingComboboxModel); + default_encoding_combobox_ = new ChromeViews::ComboBox( + default_encoding_combobox_model_.get()); + int selected_encoding_index = default_encoding_combobox_model_-> + GetSelectedEncodingIndex(profile()); + default_encoding_combobox_->SetSelectedItem(selected_encoding_index); + default_encoding_selected_ = default_encoding_combobox_model_-> + GetEncodingCharsetByIndex(selected_encoding_index); + default_encoding_combobox_->SetListener(this); + + // Now add the views. + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + encoding_contents_ = new ChromeViews::View; + GridLayout* layout = new GridLayout(encoding_contents_); + encoding_contents_->SetLayoutManager(layout); + + // Double column. + const int double_column_view_set_id = 2; + ColumnSet* column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + // Add Encoding ComboBox. + layout->StartRow(0, double_column_view_set_id); + layout->AddView(default_encoding_combobox_label_); + layout->AddView(default_encoding_combobox_, 1, 1, GridLayout::FILL, + GridLayout::CENTER); +} diff --git a/chrome/browser/views/options/fonts_page_view.h b/chrome/browser/views/options/fonts_page_view.h new file mode 100644 index 0000000..22a9bf6 --- /dev/null +++ b/chrome/browser/views/options/fonts_page_view.h @@ -0,0 +1,148 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_FONTS_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_FONTS_PAGE_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/common/pref_member.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + + +namespace ChromeViews { +class GroupboxView; +class Label; +class TableModel; +class TableView; +} + +class FontDisplayView; +class DefaultEncodingComboboxModel; + +/////////////////////////////////////////////////////////////////////////////// +// FontsPageView + +class FontsPageView : public OptionsPageView, + public ChromeViews::ComboBox::Listener, + public SelectFontDialog::Listener, + public ChromeViews::NativeButton::Listener { + public: + explicit FontsPageView(Profile* profile); + virtual ~FontsPageView(); + + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ChromeViews::ComboBox::Listener implementation: + virtual void ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index); + + // SelectFontDialog::Listener implementation: + virtual void FontSelected(const ChromeFont& font, void* params); + + // Save Changes made to relevent pref members associated with this tab. + // This is public since it is called by FontsLanguageWindowView in its + // Dialog Delegate Accept() method. + void SaveChanges(); + + protected: + // OptionsPageView implementation: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + private: + enum FontTypeBeingChanged { + NONE, + SERIF, + SANS_SERIF, + FIXED_WIDTH + }; + + // Init Dialog controls. + void InitFontLayout(); + void InitEncodingLayout(); + + bool serif_button_pressed_; + bool sans_serif_button_pressed_; + bool fixed_width_button_pressed_; + bool encoding_dropdown_clicked_; + + ChromeViews::Label* fonts_group_title_; + ChromeViews::Label* encoding_group_title_; + + ChromeViews::View* fonts_contents_; + ChromeViews::View* encoding_contents_; + + // Fonts settings. + // Select Font dialogs. + scoped_refptr<SelectFontDialog> select_font_dialog_; + + // Buttons. + ChromeViews::NativeButton* fixed_width_font_change_page_button_; + ChromeViews::NativeButton* serif_font_change_page_button_; + ChromeViews::NativeButton* sans_serif_font_change_page_button_; + + // FontDisplayView objects to display selected font. + FontDisplayView* fixed_width_font_display_view_; + FontDisplayView* serif_font_display_view_; + FontDisplayView* sans_serif_font_display_view_; + + // Labels to describe what is to be changed. + ChromeViews::Label* fixed_width_font_label_; + ChromeViews::Label* serif_font_label_; + ChromeViews::Label* sans_serif_font_label_; + + // Advanced Font names and sizes as PrefMembers. + StringPrefMember serif_name_; + StringPrefMember sans_serif_name_; + StringPrefMember fixed_width_name_; + IntegerPrefMember serif_size_; + IntegerPrefMember sans_serif_size_; + IntegerPrefMember fixed_width_size_; + StringPrefMember default_encoding_; + bool font_changed_; + + // Windows font picker flag; + FontTypeBeingChanged font_type_being_changed_; + + // Default Encoding. + scoped_ptr<DefaultEncodingComboboxModel> default_encoding_combobox_model_; + ChromeViews::Label* default_encoding_combobox_label_; + ChromeViews::ComboBox* default_encoding_combobox_; + std::wstring default_encoding_selected_; + bool default_encoding_changed_; + + DISALLOW_EVIL_CONSTRUCTORS(FontsPageView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_FONTS_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/general_page_view.cc b/chrome/browser/views/options/general_page_view.cc new file mode 100644 index 0000000..4544e67 --- /dev/null +++ b/chrome/browser/views/options/general_page_view.cc @@ -0,0 +1,1094 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/general_page_view.h" + +#include "base/gfx/png_decoder.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/dom_ui/new_tab_ui.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/shell_integration.h" +#include "chrome/browser/session_startup_pref.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/template_url_model.h" +#include "chrome/browser/url_fixer_upper.h" +#include "chrome/browser/views/keyword_editor_view.h" +#include "chrome/browser/views/options/options_group_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/radio_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/text_field.h" +#include "generated_resources.h" +#include "skia/include/SkBitmap.h" + +static const int kStartupRadioGroup = 1; +static const int kHomePageRadioGroup = 2; +static const SkColor kDefaultBrowserLabelColor = SkColorSetRGB(0, 135, 0); +static const SkColor kNotDefaultBrowserLabelColor = SkColorSetRGB(135, 0, 0); + +namespace { +std::wstring GetNewTabUIURLString() { + return UTF8ToWide(NewTabUIURL().spec()); +} +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView::DefaultBrowserWorker +// +// A helper object that handles checking if Chrome is the default browser on +// Windows and also setting it as the default browser. These operations are +// performed asynchronously on the file thread since registry access is +// involved and this can be slow. +// +class GeneralPageView::DefaultBrowserWorker + : public base::RefCountedThreadSafe<GeneralPageView::DefaultBrowserWorker> { + public: + explicit DefaultBrowserWorker(GeneralPageView* general_page_view); + + // Checks if Chrome is the default browser. + void StartCheckDefaultBrowser(); + + // Sets Chrome as the default browser. + void StartSetAsDefaultBrowser(); + + // Called to notify the worker that the view is gone. + void ViewDestroyed(); + + private: + // Functions that track the process of checking if Chrome is the default + // browser. + // |ExecuteCheckDefaultBrowser| checks the registry on the file thread. + // |CompleteCheckDefaultBrowser| notifies the view to update on the UI thread. + void ExecuteCheckDefaultBrowser(); + void CompleteCheckDefaultBrowser(bool is_default); + + // Functions that track the process of setting Chrome as the default browser. + // |ExecuteSetAsDefaultBrowser| updates the registry on the file thread. + // |CompleteSetAsDefaultBrowser| notifies the view to update on the UI thread. + void ExecuteSetAsDefaultBrowser(); + void CompleteSetAsDefaultBrowser(); + + // Updates the UI in our associated view with the current default browser + // state. + void UpdateUI(bool is_default); + + GeneralPageView* general_page_view_; + + MessageLoop* ui_loop_; + MessageLoop* file_loop_; + + DISALLOW_EVIL_CONSTRUCTORS(GeneralPageView::DefaultBrowserWorker); +}; + +GeneralPageView::DefaultBrowserWorker::DefaultBrowserWorker( + GeneralPageView* general_page_view) + : general_page_view_(general_page_view), + ui_loop_(MessageLoop::current()), + file_loop_(g_browser_process->file_thread()->message_loop()) { +} + +void GeneralPageView::DefaultBrowserWorker::StartCheckDefaultBrowser() { + general_page_view_->SetDefaultBrowserUIState(STATE_PROCESSING); + file_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &DefaultBrowserWorker::ExecuteCheckDefaultBrowser)); +} + +void GeneralPageView::DefaultBrowserWorker::StartSetAsDefaultBrowser() { + general_page_view_->SetDefaultBrowserUIState(STATE_PROCESSING); + file_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &DefaultBrowserWorker::ExecuteSetAsDefaultBrowser)); +} + +void GeneralPageView::DefaultBrowserWorker::ViewDestroyed() { + // Our associated view has gone away, so we shouldn't call back to it if + // our worker thread returns after the view is dead. + general_page_view_ = NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// DefaultBrowserWorker, private: + +void GeneralPageView::DefaultBrowserWorker::ExecuteCheckDefaultBrowser() { + DCHECK(MessageLoop::current() == file_loop_); + bool is_default = ShellIntegration::IsDefaultBrowser(); + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &DefaultBrowserWorker::CompleteCheckDefaultBrowser, is_default)); +} + +void GeneralPageView::DefaultBrowserWorker::CompleteCheckDefaultBrowser( + bool is_default) { + DCHECK(MessageLoop::current() == ui_loop_); + UpdateUI(is_default); +} + +void GeneralPageView::DefaultBrowserWorker::ExecuteSetAsDefaultBrowser() { + DCHECK(MessageLoop::current() == file_loop_); + bool result = ShellIntegration::SetAsDefaultBrowser(); + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &DefaultBrowserWorker::CompleteSetAsDefaultBrowser)); +} + +void GeneralPageView::DefaultBrowserWorker::CompleteSetAsDefaultBrowser() { + DCHECK(MessageLoop::current() == ui_loop_); + // Set as default completed, check again to make sure it stuck... + StartCheckDefaultBrowser(); +} + +void GeneralPageView::DefaultBrowserWorker::UpdateUI(bool is_default) { + if (general_page_view_) { + DefaultBrowserUIState state = + is_default ? STATE_DEFAULT : STATE_NOT_DEFAULT; + general_page_view_->SetDefaultBrowserUIState(state); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomHomePagesTableModel + +// CustomHomePagesTableModel is the model for the TableView showing the list +// of pages the user wants opened on startup. + +class CustomHomePagesTableModel : public ChromeViews::TableModel { + public: + explicit CustomHomePagesTableModel(Profile* profile); + virtual ~CustomHomePagesTableModel() {} + + // Sets the set of urls that this model contains. + void SetURLs(const std::vector<GURL>& urls); + + // Adds an entry at the specified index. + void Add(int index, const GURL& url); + + // Removes the entry at the specified index. + void Remove(int index); + + // Returns the set of urls this model contains. + std::vector<GURL> GetURLs(); + + // ChromeViews::TableModel overrides: + virtual int RowCount(); + virtual std::wstring GetText(int row, int column_id); + virtual SkBitmap GetIcon(int row); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + + private: + // Each item in the model is represented as an Entry. Entry stores the URL + // and favicon of the page. + struct Entry { + Entry() : fav_icon_handle(0) {} + + // URL of the page. + GURL url; + + // Icon for the page. + SkBitmap icon; + + // If non-zero, indicates we're loading the favicon for the page. + HistoryService::Handle fav_icon_handle; + }; + + static void InitClass(); + + // Loads the favicon for the specified entry. + void LoadFavIcon(Entry* entry); + + // Callback from history service. Updates the icon of the Entry whose + // fav_icon_handle matches handle and notifies the observer of the change. + void OnGotFavIcon(HistoryService::Handle handle, + bool know_fav_icon, + scoped_refptr<RefCountedBytes> image_data, + bool is_expired, + GURL icon_url); + + // Returns the entry whose fav_icon_handle matches handle and sets entry_index + // to the index of the entry. + Entry* GetEntryByLoadHandle(HistoryService::Handle handle, int* entry_index); + + // Set of entries we're showing. + std::vector<Entry> entries_; + + // Default icon to show when one can't be found for the URL. + static SkBitmap default_favicon_; + + // Profile used to load icons. + Profile* profile_; + + ChromeViews::TableModelObserver* observer_; + + // Used in loading favicons. + CancelableRequestConsumer fav_icon_consumer_; + + DISALLOW_EVIL_CONSTRUCTORS(CustomHomePagesTableModel); +}; + +// static +SkBitmap CustomHomePagesTableModel::default_favicon_; + +CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile) + : profile_(profile), + observer_(NULL) { + InitClass(); +} + +void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) { + entries_.resize(urls.size()); + for (size_t i = 0; i < urls.size(); ++i) { + entries_[i].url = urls[i]; + LoadFavIcon(&(entries_[i])); + } + // Complete change, so tell the view to just rebuild itself. + if (observer_) + observer_->OnModelChanged(); +} + +void CustomHomePagesTableModel::Add(int index, const GURL& url) { + DCHECK(index >= 0 && index <= RowCount()); + entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry()); + entries_[index].url = url; + LoadFavIcon(&(entries_[index])); + if (observer_) + observer_->OnItemsAdded(index, 1); +} + +void CustomHomePagesTableModel::Remove(int index) { + DCHECK(index >= 0 && index < RowCount()); + Entry* entry = &(entries_[index]); + if (entry->fav_icon_handle) { + // Pending load request, cancel it now so we don't deref a bogus pointer + // when we get loaded notification. + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (history) + history->CancelRequest(entry->fav_icon_handle); + } + entries_.erase(entries_.begin() + static_cast<size_t>(index)); + if (observer_) + observer_->OnItemsRemoved(index, 1); +} + +std::vector<GURL> CustomHomePagesTableModel::GetURLs() { + std::vector<GURL> urls(entries_.size()); + for (size_t i = 0; i < entries_.size(); ++i) + urls[i] = entries_[i].url; + return urls; +} + +int CustomHomePagesTableModel::RowCount() { + return static_cast<int>(entries_.size()); +} + +std::wstring CustomHomePagesTableModel::GetText(int row, int column_id) { + DCHECK(column_id == 0); + DCHECK(row >= 0 && row < RowCount()); + return UTF8ToWide(entries_[row].url.spec()); +} + +SkBitmap CustomHomePagesTableModel::GetIcon(int row) { + DCHECK(row >= 0 && row < RowCount()); + if (!entries_[row].icon.isNull()) + return entries_[row].icon; + return default_favicon_; +} + +void CustomHomePagesTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +void CustomHomePagesTableModel::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + default_favicon_ = *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); + initialized = true; + } +} + +void CustomHomePagesTableModel::LoadFavIcon(Entry* entry) { + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (!history) + return; + entry->fav_icon_handle = history->GetFavIconForURL( + entry->url, &fav_icon_consumer_, + NewCallback(this, &CustomHomePagesTableModel::OnGotFavIcon)); +} + +void CustomHomePagesTableModel::OnGotFavIcon( + HistoryService::Handle handle, + bool know_fav_icon, + scoped_refptr<RefCountedBytes> image_data, + bool is_expired, + GURL icon_url) { + int entry_index; + Entry* entry = GetEntryByLoadHandle(handle, &entry_index); + DCHECK(entry); + entry->fav_icon_handle = 0; + if (know_fav_icon && image_data.get() && !image_data->data.empty()) { + int width, height; + std::vector<unsigned char> decoded_data; + if (PNGDecoder::Decode(&image_data->data.front(), image_data->data.size(), + PNGDecoder::FORMAT_BGRA, &decoded_data, &width, + &height)) { + entry->icon.setConfig(SkBitmap::kARGB_8888_Config, width, height); + entry->icon.allocPixels(); + memcpy(entry->icon.getPixels(), &decoded_data.front(), + width * height * 4); + if (observer_) + observer_->OnItemsChanged(static_cast<int>(entry_index), 1); + } + } +} + +CustomHomePagesTableModel::Entry* + CustomHomePagesTableModel::GetEntryByLoadHandle( + HistoryService::Handle handle, + int* index) { + for (size_t i = 0; i < entries_.size(); ++i) { + if (entries_[i].fav_icon_handle == handle) { + *index = static_cast<int>(i); + return &entries_[i]; + } + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// SearchEngineListModel + +class SearchEngineListModel : public ChromeViews::ComboBox::Model, + public TemplateURLModelObserver { + public: + explicit SearchEngineListModel(Profile* profile); + virtual ~SearchEngineListModel(); + + // Sets the ComboBox. SearchEngineListModel needs a handle to the ComboBox + // so that when the TemplateURLModel changes the combobox can be updated. + void SetComboBox(ChromeViews::ComboBox* combo_box); + + // ChromeViews::ComboBox::Model overrides: + virtual int GetItemCount(ChromeViews::ComboBox* source); + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Returns the TemplateURL at the specified index. + const TemplateURL* GetTemplateURLAt(int index); + + TemplateURLModel* model() { return template_url_model_; } + + private: + // TemplateURLModelObserver methods. + virtual void OnTemplateURLModelChanged(); + + // Recalculates the TemplateURLs to display and notifies the combobox. + void ResetContents(); + + // Resets the selection of the combobox based on the users selected search + // engine. + void ChangeComboBoxSelection(); + + TemplateURLModel* template_url_model_; + + // The combobox hosting us. + ChromeViews::ComboBox* combo_box_; + + // The TemplateURLs we're showing. + typedef std::vector<const TemplateURL*> TemplateURLs; + TemplateURLs template_urls_; + + DISALLOW_EVIL_CONSTRUCTORS(SearchEngineListModel); +}; + +SearchEngineListModel::SearchEngineListModel(Profile* profile) + : template_url_model_(profile->GetTemplateURLModel()), + combo_box_(NULL) { + if (template_url_model_) { + template_url_model_->Load(); + template_url_model_->AddObserver(this); + } + ResetContents(); +} + +SearchEngineListModel::~SearchEngineListModel() { + if (template_url_model_) + template_url_model_->RemoveObserver(this); +} + +void SearchEngineListModel::SetComboBox(ChromeViews::ComboBox* combo_box) { + combo_box_ = combo_box; + if (template_url_model_ && template_url_model_->loaded()) + ChangeComboBoxSelection(); + else + combo_box_->SetEnabled(false); +} + +int SearchEngineListModel::GetItemCount(ChromeViews::ComboBox* source) { + return static_cast<int>(template_urls_.size()); +} + +std::wstring SearchEngineListModel::GetItemAt(ChromeViews::ComboBox* source, + int index) { + DCHECK(index < GetItemCount(combo_box_)); + return template_urls_[index]->short_name(); +} + +const TemplateURL* SearchEngineListModel::GetTemplateURLAt(int index) { + DCHECK(index >= 0 && index < static_cast<int>(template_urls_.size())); + return template_urls_[static_cast<int>(index)]; +} + +void SearchEngineListModel::OnTemplateURLModelChanged() { + ResetContents(); +} + +void SearchEngineListModel::ResetContents() { + if (!template_url_model_ || !template_url_model_->loaded()) + return; + template_urls_.clear(); + TemplateURLs model_urls = template_url_model_->GetTemplateURLs(); + for (size_t i = 0; i < model_urls.size(); ++i) { + if (model_urls[i]->ShowInDefaultList()) + template_urls_.push_back(model_urls[i]); + } + + if (combo_box_) { + combo_box_->ModelChanged(); + ChangeComboBoxSelection(); + } +} + +void SearchEngineListModel::ChangeComboBoxSelection() { + if (template_urls_.size()) { + combo_box_->SetEnabled(true); + + const TemplateURL* default_search_provider = + template_url_model_->GetDefaultSearchProvider(); + if (default_search_provider) { + TemplateURLs::iterator i = + find(template_urls_.begin(), template_urls_.end(), + default_search_provider); + if (i != template_urls_.end()) { + combo_box_->SetSelectedItem( + static_cast<int>(i - template_urls_.begin())); + } + } + } else { + combo_box_->SetEnabled(false); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, public: + +GeneralPageView::GeneralPageView(Profile* profile) + : startup_group_(NULL), + startup_homepage_radio_(NULL), + startup_last_session_radio_(NULL), + startup_custom_radio_(NULL), + startup_add_custom_page_button_(NULL), + startup_remove_custom_page_button_(NULL), + startup_use_current_page_button_(NULL), + startup_custom_pages_table_(NULL), + homepage_group_(NULL), + homepage_use_newtab_radio_(NULL), + homepage_use_url_radio_(NULL), + homepage_use_url_textfield_(NULL), + homepage_show_home_button_checkbox_(NULL), + default_search_group_(NULL), + default_search_manage_engines_button_(NULL), + default_browser_group_(NULL), + default_browser_status_label_(NULL), + default_browser_use_as_default_button_(NULL), + default_browser_worker_(new DefaultBrowserWorker(this)), + OptionsPageView(profile) { +} + +GeneralPageView::~GeneralPageView() { + profile()->GetPrefs()->RemovePrefObserver(prefs::kRestoreOnStartup, this); + profile()->GetPrefs()->RemovePrefObserver( + prefs::kURLsToRestoreOnStartup, this); + if (startup_custom_pages_table_) + startup_custom_pages_table_->SetModel(NULL); + default_browser_worker_->ViewDestroyed(); +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, ChromeViews::NativeButton::Listener implementation: + +void GeneralPageView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == startup_homepage_radio_ || + sender == startup_last_session_radio_ || + sender == startup_custom_radio_) { + SaveStartupPref(); + if (sender == startup_homepage_radio_) { + UserMetricsRecordAction(L"Options_Startup_Homepage", + profile()->GetPrefs()); + } else if (sender == startup_last_session_radio_) { + UserMetricsRecordAction(L"Options_Startup_LastSession", + profile()->GetPrefs()); + } else if (sender == startup_custom_radio_) { + UserMetricsRecordAction(L"Options_Startup_Custom", + profile()->GetPrefs()); + } + } else if (sender == startup_add_custom_page_button_) { + AddURLToStartupURLs(); + } else if (sender == startup_remove_custom_page_button_) { + RemoveURLsFromStartupURLs(); + } else if (sender == startup_use_current_page_button_) { + SetStartupURLToCurrentPage(); + } else if (sender == homepage_use_newtab_radio_) { + UserMetricsRecordAction(L"Options_Homepage_UseNewTab", + profile()->GetPrefs()); + homepage_.SetValue(GetNewTabUIURLString()); + EnableHomepageURLField(false); + } else if (sender == homepage_use_url_radio_) { + UserMetricsRecordAction(L"Options_Homepage_UseURL", + profile()->GetPrefs()); + std::wstring home_page_url = homepage_use_url_textfield_->GetText(); + if (home_page_url.empty()) + home_page_url = GetNewTabUIURLString(); + homepage_.SetValue(home_page_url); + EnableHomepageURLField(true); + } else if (sender == homepage_show_home_button_checkbox_) { + bool show_button = homepage_show_home_button_checkbox_->IsSelected(); + if (show_button) { + UserMetricsRecordAction(L"Options_Homepage_ShowHomeButton", + profile()->GetPrefs()); + } else { + UserMetricsRecordAction(L"Options_Homepage_HideHomeButton", + profile()->GetPrefs()); + } + show_home_button_.SetValue(show_button); + } else if (sender == default_browser_use_as_default_button_) { + default_browser_worker_->StartSetAsDefaultBrowser(); + UserMetricsRecordAction(L"Options_SetAsDefaultBrowser", NULL); + } else if (sender == default_search_manage_engines_button_) { + UserMetricsRecordAction(L"Options_ManageSearchEngines", NULL); + KeywordEditorView::Show(profile()); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, ChromeViews::ComboBox::Listener implementation: + +void GeneralPageView::ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, int new_index) { + if (combo_box == default_search_engine_combobox_) { + SetDefaultSearchProvider(); + UserMetricsRecordAction(L"Options_SearchEngineChanged", NULL); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, ChromeViews::TextField::Controller implementation: + +void GeneralPageView::ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents) { + if (sender == homepage_use_url_textfield_) { + // If the text field contains a valid URL, sync it to prefs. We run it + // through the fixer upper to allow input like "google.com" to be converted + // to something valid ("http://google.com"). + std::wstring url_string = URLFixerUpper::FixupURL( + homepage_use_url_textfield_->GetText(), std::wstring()); + if (GURL(url_string).is_valid()) + homepage_.SetValue(url_string); + } +} + +void GeneralPageView::HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, + UINT repeat_count, UINT flags) { + // Not necessary. +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, OptionsPageView implementation: + +void GeneralPageView::InitControlLayout() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(this); + layout->SetInsets(5, 5, 5, 5); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + layout->StartRow(0, single_column_view_set_id); + InitStartupGroup(); + layout->AddView(startup_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + InitHomepageGroup(); + layout->AddView(homepage_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + InitDefaultSearchGroup(); + layout->AddView(default_search_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + InitDefaultBrowserGroup(); + layout->AddView(default_browser_group_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Register pref observers that update the controls when a pref changes. + profile()->GetPrefs()->AddPrefObserver(prefs::kRestoreOnStartup, this); + profile()->GetPrefs()->AddPrefObserver(prefs::kURLsToRestoreOnStartup, this); + + homepage_.Init(prefs::kHomePage, profile()->GetPrefs(), this); + show_home_button_.Init(prefs::kShowHomeButton, profile()->GetPrefs(), this); +} + +void GeneralPageView::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kRestoreOnStartup) { + PrefService* prefs = profile()->GetPrefs(); + const SessionStartupPref startup_pref = + SessionStartupPref::GetStartupPref(prefs); + switch (startup_pref.type) { + case SessionStartupPref::DEFAULT: + startup_homepage_radio_->SetIsSelected(true); + EnableCustomHomepagesControls(false); + break; + + case SessionStartupPref::LAST: + startup_last_session_radio_->SetIsSelected(true); + EnableCustomHomepagesControls(false); + break; + + case SessionStartupPref::URLS: + startup_custom_radio_->SetIsSelected(true); + EnableCustomHomepagesControls(true); + break; + } + } + + // TODO(beng): Note that the kURLsToRestoreOnStartup pref is a mutable list, + // and changes to mutable lists aren't broadcast through the + // observer system, so the second half of this condition will + // never match. Once support for broadcasting such updates is + // added, this will automagically start to work, and this comment + // can be removed. + if (!pref_name || *pref_name == prefs::kURLsToRestoreOnStartup) { + PrefService* prefs = profile()->GetPrefs(); + const SessionStartupPref startup_pref = + SessionStartupPref::GetStartupPref(prefs); + startup_custom_pages_table_model_->SetURLs(startup_pref.urls); + } + + if (!pref_name || *pref_name == prefs::kHomePage) { + bool enabled = homepage_.GetValue() != GetNewTabUIURLString(); + if (enabled) { + homepage_use_url_radio_->SetIsSelected(true); + homepage_use_url_textfield_->SetText(homepage_.GetValue()); + } else { + homepage_use_newtab_radio_->SetIsSelected(true); + } + EnableHomepageURLField(enabled); + } + + if (!pref_name || *pref_name == prefs::kShowHomeButton) { + homepage_show_home_button_checkbox_->SetIsSelected( + show_home_button_.GetValue()); + } +} + +void GeneralPageView::HighlightGroup(OptionsGroup highlight_group) { + if (highlight_group == OPTIONS_GROUP_DEFAULT_SEARCH) + default_search_group_->SetHighlighted(true); +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, ChromeViews::View overrides: + +void GeneralPageView::Layout() { + // We need to Layout twice - once to get the width of the contents box... + View::Layout(); + startup_last_session_radio_->SetBounds( + 0, 0, startup_group_->GetContentsWidth(), 0); + homepage_use_newtab_radio_->SetBounds( + 0, 0, homepage_group_->GetContentsWidth(), 0); + homepage_show_home_button_checkbox_->SetBounds( + 0, 0, homepage_group_->GetContentsWidth(), 0); + default_browser_status_label_->SetBounds( + 0, 0, default_browser_group_->GetContentsWidth(), 0); + // ... and twice to get the height of multi-line items correct. + View::Layout(); +} + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView, private: + +void GeneralPageView::SetDefaultBrowserUIState(DefaultBrowserUIState state) { + bool button_enabled = state == STATE_NOT_DEFAULT; + default_browser_use_as_default_button_->SetEnabled(button_enabled); + if (state == STATE_DEFAULT) { + default_browser_status_label_->SetText( + l10n_util::GetStringF(IDS_OPTIONS_DEFAULTBROWSER_DEFAULT, + l10n_util::GetString(IDS_PRODUCT_NAME))); + default_browser_status_label_->SetColor(kDefaultBrowserLabelColor); + Layout(); + } else if (state == STATE_NOT_DEFAULT) { + default_browser_status_label_->SetText( + l10n_util::GetStringF(IDS_OPTIONS_DEFAULTBROWSER_NOTDEFAULT, + l10n_util::GetString(IDS_PRODUCT_NAME))); + default_browser_status_label_->SetColor(kNotDefaultBrowserLabelColor); + Layout(); + } +} + +void GeneralPageView::InitStartupGroup() { + startup_homepage_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_SHOW_DEFAULT_AND_NEWTAB), + kStartupRadioGroup); + startup_homepage_radio_->SetListener(this); + startup_last_session_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_SHOW_LAST_SESSION), + kStartupRadioGroup); + startup_last_session_radio_->SetListener(this); + startup_last_session_radio_->SetMultiLine(true); + startup_custom_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_SHOW_PAGES), + kStartupRadioGroup); + startup_custom_radio_->SetListener(this); + startup_add_custom_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_ADD_BUTTON)); + startup_add_custom_page_button_->SetListener(this); + startup_remove_custom_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_REMOVE_BUTTON)); + startup_remove_custom_page_button_->SetEnabled(false); + startup_remove_custom_page_button_->SetListener(this); + startup_use_current_page_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_STARTUP_USE_CURRENT)); + startup_use_current_page_button_->SetListener(this); + + startup_custom_pages_table_model_.reset( + new CustomHomePagesTableModel(profile())); + std::vector<ChromeViews::TableColumn> columns; + columns.push_back(ChromeViews::TableColumn()); + startup_custom_pages_table_ = new ChromeViews::TableView( + startup_custom_pages_table_model_.get(), columns, + ChromeViews::ICON_AND_TEXT, true, false, true); + // URLs are inherently left-to-right, so do not mirror the table. + startup_custom_pages_table_->EnableUIMirroringForRTLLanguages(false); + startup_custom_pages_table_->SetObserver(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + const int double_column_view_set_id = 1; + column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(startup_homepage_radio_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(startup_last_session_radio_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(startup_custom_radio_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, double_column_view_set_id); + layout->AddView(startup_custom_pages_table_); + + ChromeViews::View* button_stack = new ChromeViews::View; + GridLayout* button_stack_layout = new GridLayout(button_stack); + button_stack->SetLayoutManager(button_stack_layout); + + column_set = button_stack_layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(startup_add_custom_page_button_, + 1, 1, GridLayout::FILL, GridLayout::CENTER); + button_stack_layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(startup_remove_custom_page_button_, + 1, 1, GridLayout::FILL, GridLayout::CENTER); + button_stack_layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(startup_use_current_page_button_, + 1, 1, GridLayout::FILL, GridLayout::CENTER); + layout->AddView(button_stack); + + startup_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_STARTUP_GROUP_NAME), + EmptyWString(), true); +} + +void GeneralPageView::InitHomepageGroup() { + homepage_use_newtab_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_HOMEPAGE_USE_NEWTAB), + kHomePageRadioGroup); + homepage_use_newtab_radio_->SetListener(this); + homepage_use_newtab_radio_->SetMultiLine(true); + homepage_use_url_radio_ = new ChromeViews::RadioButton( + l10n_util::GetString(IDS_OPTIONS_HOMEPAGE_USE_URL), + kHomePageRadioGroup); + homepage_use_url_radio_->SetListener(this); + homepage_use_url_textfield_ = new ChromeViews::TextField; + homepage_use_url_textfield_->SetController(this); + homepage_show_home_button_checkbox_ = new ChromeViews::CheckBox( + l10n_util::GetString(IDS_OPTIONS_HOMEPAGE_SHOW_BUTTON)); + homepage_show_home_button_checkbox_->SetListener(this); + homepage_show_home_button_checkbox_->SetMultiLine(true); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + const int double_column_view_set_id = 1; + column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(homepage_use_newtab_radio_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, double_column_view_set_id); + layout->AddView(homepage_use_url_radio_); + layout->AddView(homepage_use_url_textfield_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(homepage_show_home_button_checkbox_); + + homepage_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_HOMEPAGE_GROUP_NAME), + EmptyWString(), true); +} + + +void GeneralPageView::InitDefaultSearchGroup() { + default_search_engines_model_.reset(new SearchEngineListModel(profile())); + default_search_engine_combobox_ = + new ChromeViews::ComboBox(default_search_engines_model_.get()); + default_search_engines_model_->SetComboBox(default_search_engine_combobox_); + default_search_engine_combobox_->SetListener(this); + + default_search_manage_engines_button_ = new ChromeViews::NativeButton( + l10n_util::GetString(IDS_OPTIONS_DEFAULTSEARCH_MANAGE_ENGINES_LINK)); + default_search_manage_engines_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int double_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, double_column_view_set_id); + layout->AddView(default_search_engine_combobox_); + layout->AddView(default_search_manage_engines_button_); + + default_search_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_DEFAULTSEARCH_GROUP_NAME), + EmptyWString(), true); +} + +void GeneralPageView::InitDefaultBrowserGroup() { + default_browser_status_label_ = new ChromeViews::Label; + default_browser_status_label_->SetMultiLine(true); + default_browser_status_label_->SetHorizontalAlignment( + ChromeViews::Label::ALIGN_LEFT); + default_browser_use_as_default_button_ = new ChromeViews::NativeButton( + l10n_util::GetStringF(IDS_OPTIONS_DEFAULTBROWSER_USEASDEFAULT, + l10n_util::GetString(IDS_PRODUCT_NAME))); + default_browser_use_as_default_button_->SetListener(this); + + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + ChromeViews::View* contents = new ChromeViews::View; + GridLayout* layout = new GridLayout(contents); + contents->SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(default_browser_status_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(default_browser_use_as_default_button_); + + default_browser_group_ = new OptionsGroupView( + contents, l10n_util::GetString(IDS_OPTIONS_DEFAULTBROWSER_GROUP_NAME), + EmptyWString(), false); + + default_browser_worker_->StartCheckDefaultBrowser(); +} + +void GeneralPageView::SaveStartupPref() { + SessionStartupPref pref; + + if (startup_last_session_radio_->IsSelected()) { + pref.type = SessionStartupPref::LAST; + } else if (startup_custom_radio_->IsSelected()) { + pref.type = SessionStartupPref::URLS; + } + + pref.urls = startup_custom_pages_table_model_->GetURLs(); + + SessionStartupPref::SetStartupPref(profile()->GetPrefs(), pref); +} + +void GeneralPageView::AddURLToStartupURLs() { + ShelfItemDialog* dialog = new ShelfItemDialog(this, profile(), false); + dialog->Show(GetRootWindow()); +} + +void GeneralPageView::RemoveURLsFromStartupURLs() { + for (ChromeViews::TableView::iterator i = + startup_custom_pages_table_->SelectionBegin(); + i != startup_custom_pages_table_->SelectionEnd(); ++i) { + startup_custom_pages_table_model_->Remove(*i); + } + SaveStartupPref(); +} + +void GeneralPageView::SetStartupURLToCurrentPage() { + // Remove the current entries. + while (startup_custom_pages_table_model_->RowCount()) + startup_custom_pages_table_model_->Remove(0); + + // And add all entries for all open browsers with our profile. + int add_index = 0; + for (BrowserList::const_iterator browser_i = BrowserList::begin(); + browser_i != BrowserList::end(); ++browser_i) { + Browser* browser = *browser_i; + if (browser->profile() != profile()) + continue; // Only want entries for open profile. + + for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) { + TabContents* tab = browser->GetTabContentsAt(tab_index); + if (tab->ShouldDisplayURL()) { + const GURL url = browser->GetTabContentsAt(tab_index)->GetURL(); + if (!url.is_empty()) + startup_custom_pages_table_model_->Add(add_index++, url); + } + } + } + + SaveStartupPref(); +} + +void GeneralPageView::EnableCustomHomepagesControls(bool enable) { + startup_add_custom_page_button_->SetEnabled(enable); + bool has_selected_rows = startup_custom_pages_table_->SelectedRowCount() > 0; + startup_remove_custom_page_button_->SetEnabled(enable && has_selected_rows); + startup_use_current_page_button_->SetEnabled(enable); + startup_custom_pages_table_->SetEnabled(enable); +} + +void GeneralPageView::AddBookmark(ShelfItemDialog* dialog, + const std::wstring& title, + const GURL& url) { + int index = startup_custom_pages_table_->FirstSelectedRow(); + if (index == -1) + index = startup_custom_pages_table_model_->RowCount(); + else + index++; + startup_custom_pages_table_model_->Add(index, url); + + SaveStartupPref(); +} + +void GeneralPageView::OnSelectionChanged() { + startup_remove_custom_page_button_->SetEnabled( + startup_custom_pages_table_->SelectedRowCount() > 0); +} + +void GeneralPageView::EnableHomepageURLField(bool enabled) { + if (enabled) { + homepage_use_url_textfield_->SetEnabled(true); + homepage_use_url_textfield_->SetReadOnly(false); + } else { + homepage_use_url_textfield_->SetEnabled(false); + homepage_use_url_textfield_->SetReadOnly(true); + } +} + +void GeneralPageView::SetDefaultSearchProvider() { + const int index = default_search_engine_combobox_->GetSelectedItem(); + default_search_engines_model_->model()->SetDefaultSearchProvider( + default_search_engines_model_->GetTemplateURLAt(index)); +} diff --git a/chrome/browser/views/options/general_page_view.h b/chrome/browser/views/options/general_page_view.h new file mode 100644 index 0000000..597db44 --- /dev/null +++ b/chrome/browser/views/options/general_page_view.h @@ -0,0 +1,178 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_GENERAL_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_GENERAL_PAGE_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/browser/views/shelf_item_dialog.h" +#include "chrome/common/pref_member.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/native_button.h" +#include "chrome/views/view.h" + +namespace ChromeViews { +class CheckBox; +class GroupboxView; +class Label; +class RadioButton; +class TableModel; +class TableView; +class TextField; +} +class CustomHomePagesTableModel; +class OptionsGroupView; +class SearchEngineListModel; + +/////////////////////////////////////////////////////////////////////////////// +// GeneralPageView + +class GeneralPageView : public OptionsPageView, + public ChromeViews::ComboBox::Listener, + public ChromeViews::NativeButton::Listener, + public ChromeViews::TextField::Controller, + public ShelfItemDialogDelegate, + public ChromeViews::TableViewObserver { + public: + explicit GeneralPageView(Profile* profile); + virtual ~GeneralPageView(); + + protected: + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ChromeViews::ComboBox::Listener implementation: + virtual void ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index); + + // ChromeViews::TextField::Controller implementation: + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags); + + // OptionsPageView implementation: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + virtual void HighlightGroup(OptionsGroup highlight_group); + + // ChromeViews::View overrides: + virtual void Layout(); + + private: + // The current default browser UI state + enum DefaultBrowserUIState { + STATE_PROCESSING, + STATE_DEFAULT, + STATE_NOT_DEFAULT + }; + // Updates the UI state to reflect the current default browser state. + void SetDefaultBrowserUIState(DefaultBrowserUIState state); + + // Init all the dialog controls + void InitStartupGroup(); + void InitHomepageGroup(); + void InitDefaultSearchGroup(); + void InitDefaultBrowserGroup(); + + // Saves the startup preference from that of the ui. + void SaveStartupPref(); + + // Shows a dialog allowing the user to add a new URL to the set of URLs + // launched on startup. + void AddURLToStartupURLs(); + + // Removes the selected URL from the list of startup urls. + void RemoveURLsFromStartupURLs(); + + // Resets the list of urls to launch on startup from the list of open + // browsers. + void SetStartupURLToCurrentPage(); + + // Enables/Disables the controls associated with the custom start pages + // option if that preference is not selected. + void EnableCustomHomepagesControls(bool enable); + + // ShelfItemDialogDelegate. Adds the URL to the list of startup urls. + virtual void AddBookmark(ShelfItemDialog* dialog, + const std::wstring& title, + const GURL& url); + + // Invoked when the selection of the table view changes. Updates the enabled + // property of the remove button. + virtual void OnSelectionChanged(); + + // Enables or disables the field for entering a custom homepage URL. + void EnableHomepageURLField(bool enabled); + + // Sets the default search provider for the selected item in the combobox. + void SetDefaultSearchProvider(); + + // Controls for the Startup group + OptionsGroupView* startup_group_; + ChromeViews::RadioButton* startup_homepage_radio_; + ChromeViews::RadioButton* startup_last_session_radio_; + ChromeViews::RadioButton* startup_custom_radio_; + ChromeViews::NativeButton* startup_add_custom_page_button_; + ChromeViews::NativeButton* startup_remove_custom_page_button_; + ChromeViews::NativeButton* startup_use_current_page_button_; + ChromeViews::TableView* startup_custom_pages_table_; + scoped_ptr<CustomHomePagesTableModel> startup_custom_pages_table_model_; + + // Controls for the Home Page group + OptionsGroupView* homepage_group_; + ChromeViews::RadioButton* homepage_use_newtab_radio_; + ChromeViews::RadioButton* homepage_use_url_radio_; + ChromeViews::TextField* homepage_use_url_textfield_; + ChromeViews::CheckBox* homepage_show_home_button_checkbox_; + StringPrefMember homepage_; + BooleanPrefMember show_home_button_; + + // Controls for the Default Search group + OptionsGroupView* default_search_group_; + ChromeViews::ComboBox* default_search_engine_combobox_; + ChromeViews::NativeButton* default_search_manage_engines_button_; + scoped_ptr<SearchEngineListModel> default_search_engines_model_; + + // Controls for the Default Browser group + OptionsGroupView* default_browser_group_; + ChromeViews::Label* default_browser_status_label_; + ChromeViews::NativeButton* default_browser_use_as_default_button_; + + // The helper object that performs default browser set/check tasks. + class DefaultBrowserWorker; + friend DefaultBrowserWorker; + scoped_refptr<DefaultBrowserWorker> default_browser_worker_; + + DISALLOW_EVIL_CONSTRUCTORS(GeneralPageView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_GENERAL_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/language_combobox_model.cc b/chrome/browser/views/options/language_combobox_model.cc new file mode 100644 index 0000000..7743a8c --- /dev/null +++ b/chrome/browser/views/options/language_combobox_model.cc @@ -0,0 +1,190 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/language_combobox_model.h" + +#include "base/string_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/common/pref_service.h" +#include "unicode/uloc.h" + +#include "generated_resources.h" + +/////////////////////////////////////////////////////////////////////////////// +// LanguageComboboxModel used to populate a combobox with native names +// corresponding to the language code (e.g. English (United States) for en-US) +// +LanguageComboboxModel::LanguageComboboxModel() + : profile_(NULL) { + // Enumerate the languages we know about. + const std::vector<std::wstring>& locale_codes = + l10n_util::GetAvailableLocales(); + InitNativeNames(locale_codes); +} + +// Overload using a profile and customized local_codes vector. +LanguageComboboxModel::LanguageComboboxModel( + Profile* profile, const std::vector<std::wstring>& locale_codes) + : profile_(profile) { + InitNativeNames(locale_codes); +} + +void LanguageComboboxModel::InitNativeNames(const std::vector<std::wstring>& + locale_codes) { + const std::string app_locale = WideToASCII( + g_browser_process->GetApplicationLocale()); + for (size_t i = 0; i < locale_codes.size(); ++i) { + std::string locale_code_str = WideToASCII(locale_codes[i]); + const char* locale_code = locale_code_str.c_str(); + + // Internally, we use the language code of zh-CN and zh-TW, but we want the + // display names to be Chinese (Simplified) and Chinese (Traditional). To + // do that, we use zh-hans and zh-hant when using ICU's + // uloc_getDisplayName. + if (locale_code_str == "zh-CN") { + locale_code = "zh-hans"; + } else if (locale_code_str == "zh-TW") { + locale_code = "zh-hant"; + } + + UErrorCode error = U_ZERO_ERROR; + const int buffer_size = 1024; + std::wstring name_local; + int actual_size = uloc_getDisplayName(locale_code, app_locale.c_str(), + WriteInto(&name_local, buffer_size + 1), buffer_size, &error); + DCHECK(U_SUCCESS(error)); + name_local.resize(actual_size); + + std::wstring name_native; + actual_size = uloc_getDisplayName(locale_code, locale_code, + WriteInto(&name_native, buffer_size + 1), buffer_size, &error); + DCHECK(U_SUCCESS(error)); + name_native.resize(actual_size); + + locale_names_.push_back(name_local); + native_names_[name_local] = LocaleData(name_native, locale_codes[i]); + } + + // Sort using locale specific sorter. + l10n_util::SortStrings(g_browser_process->GetApplicationLocale(), + &locale_names_); +} + +// Overridden from ChromeViews::Combobox::Model: +int LanguageComboboxModel::GetItemCount(ChromeViews::ComboBox* source) { + return static_cast<int>(locale_names_.size()); +} + +std::wstring LanguageComboboxModel::GetItemAt(ChromeViews::ComboBox* source, + int index) { + DCHECK(static_cast<int>(locale_names_.size()) > index); + LocaleDataMap::const_iterator it = + native_names_.find(locale_names_[index]); + DCHECK(it != native_names_.end()); + + // If the name is the same in the native language and local language, + // don't show it twice. + if (it->second.native_name == locale_names_[index]) + return it->second.native_name; + + // We must add directionality formatting to both the native name and the + // locale name in order to avoid text rendering problems such as misplaced + // parentheses or languages appearing in the wrong order. + std::wstring locale_name_localized; + std::wstring locale_name; + if (l10n_util::AdjustStringForLocaleDirection(locale_names_[index], + &locale_name_localized)) + locale_name.assign(locale_name_localized); + else + locale_name.assign(locale_names_[index]); + + std::wstring native_name_localized; + std::wstring native_name; + if (l10n_util::AdjustStringForLocaleDirection(it->second.native_name, + &native_name_localized)) + native_name.assign(native_name_localized); + else + native_name.assign(it->second.native_name); + + // We used to have a localizable template here, but none of translators + // changed the format. We also want to switch the order of locale_name + // and native_name without going back to translators. + std::wstring formatted_item; + SStringPrintf(&formatted_item, L"%s - %s", locale_name.c_str(), + native_name.c_str()); + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + // Somehow combo box (even with LAYOUTRTL flag) doesn't get this + // right so we add RTL BDO (U+202E) to set the direction + // explicitly. + formatted_item.insert(0, L"\x202E"); + return formatted_item; +} + +// Return the locale for the given index. E.g., may return pt-BR. +std::wstring LanguageComboboxModel::GetLocaleFromIndex(int index) { + DCHECK(static_cast<int>(locale_names_.size()) > index); + LocaleDataMap::const_iterator it = + native_names_.find(locale_names_[index]); + DCHECK(it != native_names_.end()); + + return it->second.locale_code; +} + +int LanguageComboboxModel::GetIndexFromLocale(const std::wstring& locale) { + for (size_t i = 0; i < locale_names_.size(); ++i) { + LocaleDataMap::const_iterator it = + native_names_.find(locale_names_[i]); + DCHECK(it != native_names_.end()); + if (it->second.locale_code == locale) + return static_cast<int>(i); + } + return -1; +} + +// Returns the index of the language currently specified in the user's +// preference file. Note that it's possible for language A to be picked +// while chrome is currently in language B if the user specified language B +// via --lang. Since --lang is not a persistent setting, it seems that it +// shouldn't be reflected in this combo box. We return -1 if the value in +// the pref doesn't map to a know language (possible if the user edited the +// prefs file manually). +int LanguageComboboxModel::GetSelectedLanguageIndex(const std::wstring& + prefs) { + PrefService* local_state; + if (!profile_) + local_state = g_browser_process->local_state(); + else + local_state = profile_->GetPrefs(); + + DCHECK(local_state); + const std::wstring& current_lang = local_state->GetString(prefs.c_str()); + + return GetIndexFromLocale(current_lang); +} diff --git a/chrome/browser/views/options/language_combobox_model.h b/chrome/browser/views/options/language_combobox_model.h new file mode 100644 index 0000000..dc03f91 --- /dev/null +++ b/chrome/browser/views/options/language_combobox_model.h @@ -0,0 +1,95 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGE_COMBOBOX_MODEL_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGE_COMBOBOX_MODEL_H__ + +#include "chrome/browser/profile.h" +#include "chrome/views/combo_box.h" + +/////////////////////////////////////////////////////////////////////////////// +// LanguageComboboxModel +// The model that fills the dropdown of valid UI languages. +class LanguageComboboxModel : public ChromeViews::ComboBox::Model { + public: + struct LocaleData { + LocaleData() { } + LocaleData(const std::wstring& name, const std::wstring& code) + : native_name(name), locale_code(code) { } + + std::wstring native_name; + std::wstring locale_code; // E.g., en-us. + }; + typedef std::map<std::wstring, LocaleData> LocaleDataMap; + + LanguageComboboxModel(); + + // Overload using a profile and customized local_codes vector. + LanguageComboboxModel(Profile* profile, + const std::vector<std::wstring>& locale_codes); + + virtual ~LanguageComboboxModel() {} + + void InitNativeNames(const std::vector<std::wstring>& locale_codes); + + // Overridden from ChromeViews::Combobox::Model: + virtual int GetItemCount(ChromeViews::ComboBox* source); + + virtual std::wstring GetItemAt(ChromeViews::ComboBox* source, int index); + + // Return the locale for the given index. E.g., may return pt-BR. + std::wstring GetLocaleFromIndex(int index); + + // Returns the index for the given locale. Returns -1 if the locale is not + // in the combobox model. + int GetIndexFromLocale(const std::wstring& locale); + + // Returns the index of the language currently specified in the user's + // preference file. Note that it's possible for language A to be picked + // while chrome is currently in language B if the user specified language B + // via --lang. Since --lang is not a persistent setting, it seems that it + // shouldn't be reflected in this combo box. We return -1 if the value in + // the pref doesn't map to a know language (possible if the user edited the + // prefs file manually). + int GetSelectedLanguageIndex(const std::wstring& prefs); + + private: + // The name all the locales in the current application locale. + std::vector<std::wstring> locale_names_; + + // A map of some extra data (LocaleData) keyed off the name of the locale. + LocaleDataMap native_names_; + + // Profile. + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(LanguageComboboxModel); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGE_COMBOBOX_MODEL_H__ diff --git a/chrome/browser/views/options/languages_page_view.cc b/chrome/browser/views/options/languages_page_view.cc new file mode 100644 index 0000000..2c9ef19 --- /dev/null +++ b/chrome/browser/views/options/languages_page_view.cc @@ -0,0 +1,778 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <windows.h> +#include <shlobj.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "chrome/browser/views/options/languages_page_view.h" + +#include "base/file_util.h" +#include "base/string_util.h" +#include "base/gfx/native_theme.h" +#include "base/gfx/skia_utils.h" +#include "base/string_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/shell_dialogs.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/options/language_combobox_model.h" +#include "chrome/browser/views/password_manager_view.h" +#include "chrome/browser/views/restart_message_box.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/checkbox.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/native_button.h" +#include "chrome/views/radio_button.h" +#include "chrome/views/tabbed_pane.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view_container.h" +#include "generated_resources.h" +#include "skia/include/SkBitmap.h" +#include "unicode/uloc.h" + +static const wchar_t* const g_supported_spellchecker_languages[] = { + L"en-US", + L"en-GB", + L"fr-FR", + L"it-IT", + L"de-DE", + L"es-ES", + L"nl-NL", + L"pt-BR", + L"ru-RU", + L"pl-PL", + // L"th-TH", // Not to be included in Spellchecker as per B=1277824 + L"sv-SE", + L"da-DK", + L"pt-PT", + L"ro-RO", + // L"hu-HU", // Not to be included in Spellchecker as per B=1277824 + // L"he-IL", // Not to be included in Spellchecker as per B=1252241 + L"id-ID", + L"cs-CZ", + L"el-GR", + L"nb-NO", + L"vi-VN", + // L"bg-BG", // Not to be included in Spellchecker as per B=1277824 + L"hr-HR", + L"lt-LT", + L"sk-SK", + L"sl-SI", + L"ca-ES" + L"lv-LV", + // L"uk-UA", // Not to be included in Spellchecker as per B=1277824 + L"hi-IN", + // + // TODO(Sidchat): Uncomment/remove languages as and when they get resolved. + // +}; + +static const wchar_t* const accept_language_list[] = { + L"af", // Afrikaans + L"am", // Amharic + L"ar", // Arabic + L"az", // Azerbaijani + L"be", // Belarusian + L"bg", // Bulgarian + L"bh", // Bihari + L"bn", // Bengali + L"br", // Breton + L"bs", // Bosnian + L"ca", // Catalan + L"co", // Corsican + L"cs", // Czech + L"cy", // Welsh + L"da", // Danish + L"de", // German + L"el", // Greek + L"en", // English + L"en-GB", // English (UK) + L"en-US", // English (US) + L"eo", // Esperanto + // TODO(jungshik) : Do we want to list all es-Foo for Latin-American + // Spanish speaking countries? + L"es", // Spanish + L"et", // Estonian + L"eu", // Basque + L"fa", // Persian + L"fi", // Finnish + L"fil", // Filipino + L"fo", // Faroese + L"fr", // French + L"fy", // Frisian + L"ga", // Irish + L"gd", // Scots Gaelic + L"gl", // Galician + L"gn", // Guarani + L"gu", // Gujarati + L"he", // Hebrew + L"hi", // Hindi + L"hr", // Croatian + L"hu", // Hungarian + L"hy", // Armenian + L"ia", // Interlingua + L"id", // Indonesian + L"is", // Icelandic + L"it", // Italian + L"ja", // Japanese + L"jw", // Javanese + L"ka", // Georgian + L"kk", // Kazakh + L"km", // Cambodian + L"kn", // Kannada + L"ko", // Korean + L"ku", // Kurdish + L"ky", // Kyrgyz + L"la", // Latin + L"ln", // Lingala + L"lo", // Laothian + L"lt", // Lithuanian + L"lv", // Latvian + L"mk", // Macedonian + L"ml", // Malayalam + L"mn", // Mongolian + L"mo", // Moldavian + L"mr", // Marathi + L"ms", // Malay + L"mt", // Maltese + L"nb", // Norwegian (Bokmal) + L"ne", // Nepali + L"nl", // Dutch + L"nn", // Norwegian (Nynorsk) + L"no", // Norwegian + L"oc", // Occitan + L"or", // Oriya + L"pa", // Punjabi + L"pl", // Polish + L"ps", // Pashto + L"pt", // Portuguese + L"pt-BR", // Portuguese (Brazil) + L"pt-PT", // Portuguese (Portugal) + L"qu", // Quechua + L"rm", // Romansh + L"ro", // Romanian + L"ru", // Russian + L"sd", // Sindhi + L"sh", // Serbo-Croatian + L"si", // Sinhalese + L"sk", // Slovak + L"sl", // Slovenian + L"sn", // Shona + L"so", // Somali + L"sq", // Albanian + L"sr", // Serbian + L"st", // Sesotho + L"su", // Sundanese + L"sv", // Swedish + L"sw", // Swahili + L"ta", // Tamil + L"te", // Telugu + L"tg", // Tajik + L"th", // Thai + L"ti", // Tigrinya + L"tk", // Turkmen + L"to", // Tonga + L"tr", // Turkish + L"tt", // Tatar + L"tw", // Twi + L"ug", // Uighur + L"uk", // Ukrainian + L"ur", // Urdu + L"uz", // Uzbek + L"vi", // Vietnamese + L"xh", // Xhosa + L"yi", // Yiddish + L"yo", // Yoruba + L"zh", // Chinese + L"zh-CN", // Chinese (Simplified) + L"zh-TW", // Chinese (Traditional) + L"zu", // Zulu +}; + +/////////////////////////////////////////////////////////////////////////////// +// AddLanguageWindowView +// +// This opens another window from where a new accept language can be selected. +// +class AddLanguageWindowView : public ChromeViews::View, + public ChromeViews::ComboBox::Listener, + public ChromeViews::DialogDelegate { + public: + AddLanguageWindowView(LanguagesPageView* language_delegate, Profile* profile); + ChromeViews::Window* container() const { return container_; } + void set_container(ChromeViews::Window* container) { + container_ = container; + } + + // ChromeViews::DialogDelegate methods. + virtual bool Accept(); + virtual std::wstring GetWindowTitle() const; + + // ChromeViews::WindowDelegate method. + virtual bool IsModal() const { return true; } + + // ChromeViews::ComboBox::Listener implementation: + virtual void ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index); + + // ChromeViews::View overrides. + virtual void Layout(); + virtual void GetPreferredSize(CSize *out); + + protected: + virtual void ViewHierarchyChanged(bool is_add, ChromeViews::View* parent, + ChromeViews::View* child); + + private: + void Init(); + + // The Options dialog window. + ChromeViews::Window* container_; + + // Used for Call back to LanguagePageView that language has been selected. + LanguagesPageView* language_delegate_; + std::wstring accept_language_selected_; + + // Combobox and its corresponding model. + scoped_ptr<LanguageComboboxModel> accept_language_combobox_model_; + ChromeViews::ComboBox* accept_language_combobox_; + + // The Profile associated with this window. + Profile* profile_; + + DISALLOW_EVIL_CONSTRUCTORS(AddLanguageWindowView); +}; + +static const int kDialogPadding = 7; +static int kDefaultWindowWidthChars = 60; +static int kDefaultWindowHeightLines = 3; + +AddLanguageWindowView::AddLanguageWindowView(LanguagesPageView* language_delegate, + Profile* profile) + : profile_(profile->GetOriginalProfile()), + language_delegate_(language_delegate), + accept_language_combobox_(NULL) { + Init(); + + // Initialize accept_language_selected_ to the first index in drop down. + accept_language_selected_ = accept_language_combobox_model_-> + GetLocaleFromIndex(0); +} + +std::wstring AddLanguageWindowView::GetWindowTitle() const { + return l10n_util::GetString(IDS_FONT_LANGUAGE_SETTING_LANGUAGES_TAB_TITLE); +} + +bool AddLanguageWindowView::Accept() { + if (language_delegate_) { + language_delegate_->OnAddLanguage(accept_language_selected_); + } + return true; +} + +void AddLanguageWindowView::ItemChanged(ChromeViews::ComboBox* combo_box, + int prev_index, + int new_index) { + accept_language_selected_ = accept_language_combobox_model_-> + GetLocaleFromIndex(new_index); +} + +void AddLanguageWindowView::Layout() { + CSize sz; + accept_language_combobox_->GetPreferredSize(&sz); + accept_language_combobox_->SetBounds(kDialogPadding, kDialogPadding, + GetWidth() - 2*kDialogPadding, sz.cy); +} + +void AddLanguageWindowView::GetPreferredSize(CSize* out) { + DCHECK(out); + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + out->cx = font.ave_char_width() * kDefaultWindowWidthChars; + out->cy = font.height() * kDefaultWindowHeightLines; +} + +void AddLanguageWindowView::ViewHierarchyChanged( + bool is_add, ChromeViews::View* parent, ChromeViews::View* child) { + // Can't init before we're inserted into a ViewContainer, because we require + // a HWND to parent native child controls to. + if (is_add && child == this) + Init(); +} + +void AddLanguageWindowView::Init() { + // Determine Locale Codes. + std::vector<std::wstring> locale_codes; + const std::wstring app_locale = g_browser_process->GetApplicationLocale(); + for (size_t i = 0; i < arraysize(accept_language_list); ++i) { + std::wstring local_name = + l10n_util::GetLocalName(accept_language_list[i], app_locale, false); + // This is a hack. If ICU doesn't have a translated name for + // this language, GetLocalName will just return the language code. + // In that case, we skip it. + // TODO(jungshik) : Put them at the of the list with language codes + // enclosed by brackets. + if (local_name != accept_language_list[i]) + locale_codes.push_back(accept_language_list[i]); + } + accept_language_combobox_model_.reset(new LanguageComboboxModel( + profile_, locale_codes)); + accept_language_combobox_ = new ChromeViews::ComboBox( + accept_language_combobox_model_.get()); + accept_language_combobox_->SetSelectedItem(0); + accept_language_combobox_->SetListener(this); + AddChildView(accept_language_combobox_); +} + +class LanguageOrderTableModel : public ChromeViews::TableModel { + public: + LanguageOrderTableModel(); + + // Set Language List. + void SetAcceptLanguagesString(const std::wstring& language_list); + + // Add at the end. + void Add(const std::wstring& language); + + // Removes the entry at the specified index. + void Remove(int index); + + // Move down the entry at the specified index. + void MoveDown(int index); + + // Move up the entry at the specified index. + void MoveUp(int index); + + // Returns the set of languagess this model contains. + std::wstring GetLanguageList() { return VectorToList(languages_); } + + // ChromeViews::TableModel overrides: + virtual int RowCount(); + virtual std::wstring GetText(int row, int column_id); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + + private: + // This method converts a comma separated list to a vector of strings. + void ListToVector(const std::wstring& list, + std::vector<std::wstring>* vector); + + // This method returns a comma separated string given a string vector. + std::wstring VectorToList(const std::vector<std::wstring>& vector); + + // Set of entries we're showing. + std::vector<std::wstring> languages_; + std::wstring comma_separated_language_list_; + + ChromeViews::TableModelObserver* observer_; + + DISALLOW_EVIL_CONSTRUCTORS(LanguageOrderTableModel); +}; + +LanguageOrderTableModel::LanguageOrderTableModel() + : observer_(NULL) { +} + +void LanguageOrderTableModel::SetAcceptLanguagesString( + const std::wstring& language_list) { + std::vector<std::wstring> languages_vector; + ListToVector(language_list, &languages_vector); + for (int i = 0; i < static_cast<int>(languages_vector.size()); i++) { + Add(languages_vector.at(i)); + } +} + +void LanguageOrderTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +std::wstring LanguageOrderTableModel::GetText(int row, int column_id) { + DCHECK(row >= 0 && row < RowCount()); + const std::wstring app_locale = g_browser_process->GetApplicationLocale(); + return l10n_util::GetLocalName(languages_.at(row), app_locale, true); +} + +void LanguageOrderTableModel::Add(const std::wstring& language) { + if (language.empty()) + return; + // Check for selecting duplicated language. + for (std::vector<std::wstring>::const_iterator cit = languages_.begin(); + cit != languages_.end(); ++cit) + if (*cit == language) + return; + languages_.push_back(language); + if (observer_) + observer_->OnItemsAdded(RowCount() - 1, 1); +} + +void LanguageOrderTableModel::Remove(int index) { + DCHECK(index >= 0 && index < RowCount()); + languages_.erase(languages_.begin() + index); + if (observer_) + observer_->OnItemsRemoved(index, 1); +} + +void LanguageOrderTableModel::MoveDown(int index) { + if (index < 0 || index >= RowCount() - 1) + return; + std::wstring item = languages_.at(index); + languages_.erase(languages_.begin() + index); + if (index == RowCount() - 1) + languages_.push_back(item); + else + languages_.insert(languages_.begin() + index + 1, item); + if (observer_) + observer_->OnItemsChanged(0, RowCount()); +} + +void LanguageOrderTableModel::MoveUp(int index) { + if (index <= 0 || index >= static_cast<int>(languages_.size())) + return; + std::wstring item = languages_.at(index); + languages_.erase(languages_.begin() + index); + languages_.insert(languages_.begin() + index - 1, item); + if (observer_) + observer_->OnItemsChanged(0, RowCount()); +} + +int LanguageOrderTableModel::RowCount() { + return static_cast<int>(languages_.size()); +} + +void LanguageOrderTableModel::ListToVector(const std::wstring& list, + std::vector<std::wstring>* vector) { + SplitString(list, L',', vector); +} + +std::wstring LanguageOrderTableModel::VectorToList( + const std::vector<std::wstring>& vector) { + std::wstring list; + for (int i = 0 ; i < static_cast<int>(vector.size()) ; i++) { + list += vector.at(i); + if (i != vector.size() - 1) + list += ','; + } + return list; +} + +LanguagesPageView::LanguagesPageView(Profile* profile) + : languages_instructions_(NULL), + languages_contents_(NULL), + language_order_table_(NULL), + add_button_(NULL), + remove_button_(NULL), + move_up_button_(NULL), + move_down_button_(NULL), + button_stack_(NULL), + language_info_label_(NULL), + ui_language_label_(NULL), + change_ui_language_combobox_(NULL), + change_dictionary_language_combobox_(NULL), + dictionary_language_label_(NULL), + OptionsPageView(profile), + language_table_edited_(false) { + accept_languages_.Init(prefs::kAcceptLanguages, + profile->GetPrefs(), NULL); +} + +LanguagesPageView::~LanguagesPageView() { + if (language_order_table_) + language_order_table_->SetModel(NULL); +} + +void LanguagesPageView::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == move_up_button_) { + OnMoveUpLanguage(); + language_table_edited_ = true; + } else if (sender == move_down_button_) { + OnMoveDownLanguage(); + language_table_edited_ = true; + } else if (sender == remove_button_) { + OnRemoveLanguage(); + language_table_edited_ = true; + } else if (sender == add_button_) { + AddLanguageWindowView* instance = new AddLanguageWindowView(this, profile()); + HWND parent_hwnd = GetViewContainer()->GetHWND(); + ChromeViews::Window* w = + ChromeViews::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), + instance, instance); + w->Show(); + language_table_edited_ = true; + } +} + +void LanguagesPageView::OnAddLanguage(const std::wstring& new_language) { + language_order_table_model_->Add(new_language); + language_order_table_->Select(language_order_table_model_->RowCount() - 1); + OnSelectionChanged(); +} + +void LanguagesPageView::InitControlLayout() { + // Define the buttons. + add_button_ = new ChromeViews::NativeButton(l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_SELECTOR_ADD_BUTTON_LABEL)); + add_button_->SetListener(this); + remove_button_ = new ChromeViews::NativeButton(l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_SELECTOR_REMOVE_BUTTON_LABEL)); + remove_button_->SetEnabled(false); + remove_button_->SetListener(this); + move_up_button_ = new ChromeViews::NativeButton(l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_SELECTOR_MOVEUP_BUTTON_LABEL)); + move_up_button_->SetEnabled(false); + move_up_button_->SetListener(this); + move_down_button_ = new ChromeViews::NativeButton(l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_SELECTOR_MOVEDOWN_BUTTON_LABEL)); + move_down_button_->SetEnabled(false); + move_down_button_->SetListener(this); + + languages_contents_ = new ChromeViews::View; + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + const int single_column_view_set_id = 0; + ColumnSet* column_set = layout->AddColumnSet(single_column_view_set_id); + + // Add the instructions label. + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + languages_instructions_ = new ChromeViews::Label( + l10n_util::GetString( + IDS_FONT_LANGUAGE_SETTING_LANGUAGES_INSTRUCTIONS)); + languages_instructions_->SetMultiLine(true); + languages_instructions_->SetHorizontalAlignment( + ChromeViews::Label::ALIGN_LEFT); + layout->StartRow(0, single_column_view_set_id); + layout->AddView(languages_instructions_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Add two columns - for table, and for button stack. + std::vector<ChromeViews::TableColumn> columns; + columns.push_back(ChromeViews::TableColumn()); + language_order_table_model_.reset(new LanguageOrderTableModel); + language_order_table_ = new ChromeViews::TableView( + language_order_table_model_.get(), columns, + ChromeViews::TEXT_ONLY, false, true, true); + language_order_table_->SetObserver(this); + + const int double_column_view_set_id = 1; + column_set = layout->AddColumnSet(double_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, double_column_view_set_id); + + // Add the table to the the first column. + layout->AddView(language_order_table_); + + // Now add the four buttons to the second column. + button_stack_ = new ChromeViews::View; + GridLayout* button_stack_layout = new GridLayout(button_stack_); + button_stack_->SetLayoutManager(button_stack_layout); + + column_set = button_stack_layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(move_up_button_, 1, 1, GridLayout::FILL, + GridLayout::CENTER); + button_stack_layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(move_down_button_, 1, 1, GridLayout::FILL, + GridLayout::CENTER); + button_stack_layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(remove_button_, 1, 1, GridLayout::FILL, + GridLayout::CENTER); + button_stack_layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + button_stack_layout->StartRow(0, single_column_view_set_id); + button_stack_layout->AddView(add_button_, 1, 1, GridLayout::FILL, + GridLayout::CENTER); + + layout->AddView(button_stack_); + + layout->AddPaddingRow(0, kUnrelatedControlLargeVerticalSpacing); + + language_info_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_CHROME_LANGUAGE_INFO)); + language_info_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + ui_language_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_CHROME_UI_LANGUAGE)); + ui_language_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + ui_language_model_.reset(new LanguageComboboxModel); + change_ui_language_combobox_ = + new ChromeViews::ComboBox(ui_language_model_.get()); + change_ui_language_combobox_->SetListener(this); + dictionary_language_label_ = new ChromeViews::Label( + l10n_util::GetString(IDS_OPTIONS_CHROME_DICTIONARY_LANGUAGE)); + dictionary_language_label_->SetHorizontalAlignment( + ChromeViews::Label::ALIGN_LEFT); + + layout->StartRow(0, single_column_view_set_id); + layout->AddView(language_info_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + const int double_column_view_set_2_id = 2; + column_set = layout->AddColumnSet(double_column_view_set_2_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + // Determine Locale Codes. + std::vector<std::wstring> locale_codes; + for (size_t i = 0; i < arraysize(g_supported_spellchecker_languages); ++i) + locale_codes.push_back(g_supported_spellchecker_languages[i]); + dictionary_language_model_.reset(new LanguageComboboxModel(profile(), + locale_codes)); + change_dictionary_language_combobox_ = + new ChromeViews::ComboBox(dictionary_language_model_.get()); + change_dictionary_language_combobox_->SetListener(this); + + layout->StartRow(0, double_column_view_set_2_id); + layout->AddView(ui_language_label_); + layout->AddView(change_ui_language_combobox_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, double_column_view_set_2_id); + layout->AddView(dictionary_language_label_); + layout->AddView(change_dictionary_language_combobox_); + + // Init member prefs so we can update the controls if prefs change. + app_locale_.Init(prefs::kApplicationLocale, + g_browser_process->local_state(), this); + dictionary_language_.Init(prefs::kSpellCheckDictionary, + profile()->GetPrefs(), this); +} + +void LanguagesPageView::NotifyPrefChanged(const std::wstring* pref_name) { + if (!pref_name || *pref_name == prefs::kAcceptLanguages) { + language_order_table_model_->SetAcceptLanguagesString( + accept_languages_.GetValue()); + } + if (!pref_name || *pref_name == prefs::kApplicationLocale) { + int index = ui_language_model_->GetSelectedLanguageIndex( + prefs::kApplicationLocale); + if (-1 == index) { + // The pref value for locale isn't valid. Use the current app locale + // (which is what we're currently using). + index = ui_language_model_->GetIndexFromLocale( + g_browser_process->GetApplicationLocale()); + } + DCHECK(-1 != index); + change_ui_language_combobox_->SetSelectedItem(index); + } + if (!pref_name || *pref_name == prefs::kSpellCheckDictionary) { + int index = dictionary_language_model_->GetSelectedLanguageIndex( + prefs::kSpellCheckDictionary); + change_dictionary_language_combobox_->SetSelectedItem(index); + } +} + +void LanguagesPageView::ItemChanged(ChromeViews::ComboBox* sender, + int prev_index, + int new_index) { + if (sender == change_ui_language_combobox_) { + UserMetricsRecordAction(L"Options_AppLanguage", + g_browser_process->local_state()); + app_locale_.SetValue(ui_language_model_->GetLocaleFromIndex(new_index)); + RestartMessageBox::ShowMessageBox(GetRootWindow()); + } else if (sender == change_dictionary_language_combobox_) { + UserMetricsRecordAction(L"Options_DictionaryLanguage", + profile()->GetPrefs()); + dictionary_language_.SetValue(dictionary_language_model_-> + GetLocaleFromIndex(new_index)); + RestartMessageBox::ShowMessageBox(GetRootWindow()); + } +} + +void LanguagesPageView::OnSelectionChanged() { + move_up_button_->SetEnabled(language_order_table_->FirstSelectedRow() > 0 && + language_order_table_->SelectedRowCount() == 1); + move_down_button_->SetEnabled(language_order_table_->FirstSelectedRow() < + language_order_table_->RowCount() - 1 && + language_order_table_->SelectedRowCount() == + 1); + remove_button_->SetEnabled(language_order_table_->SelectedRowCount() > 0); +} + +void LanguagesPageView::OnRemoveLanguage() { + int item_selected = 0; + for (ChromeViews::TableView::iterator i = + language_order_table_->SelectionBegin(); + i != language_order_table_->SelectionEnd(); ++i) { + language_order_table_model_->Remove(*i); + item_selected = *i; + } + + move_up_button_->SetEnabled(false); + move_down_button_->SetEnabled(false); + remove_button_->SetEnabled(false); + int items_left = language_order_table_model_->RowCount(); + if (items_left <= 0) + return; + if (item_selected > items_left - 1) + item_selected = items_left - 1; + language_order_table_->Select(item_selected); + OnSelectionChanged(); +} + +void LanguagesPageView::OnMoveDownLanguage() { + int item_selected = language_order_table_->FirstSelectedRow(); + language_order_table_model_->MoveDown(item_selected); + language_order_table_->Select(item_selected + 1); + OnSelectionChanged(); +} + +void LanguagesPageView::OnMoveUpLanguage() { + int item_selected = language_order_table_->FirstSelectedRow(); + language_order_table_model_->MoveUp(item_selected); + language_order_table_->Select(item_selected - 1); + + OnSelectionChanged(); +} + +void LanguagesPageView::SaveChanges() { + if (language_order_table_model_.get() && language_table_edited_) + accept_languages_.SetValue(language_order_table_model_->GetLanguageList()); +} diff --git a/chrome/browser/views/options/languages_page_view.h b/chrome/browser/views/options/languages_page_view.h new file mode 100644 index 0000000..8f87ba0 --- /dev/null +++ b/chrome/browser/views/options/languages_page_view.h @@ -0,0 +1,123 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGES_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGES_PAGE_VIEW_H__ + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/common/pref_member.h" +#include "chrome/views/combo_box.h" +#include "chrome/views/native_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/view.h" + +namespace ChromeViews { +class Label; +class TableModel; +class TableView; +} + +class LanguageComboboxModel; +class LanguageOrderTableModel; +class AddLanguageView; + +/////////////////////////////////////////////////////////////////////////////// +// LanguagesPageView + +class LanguagesPageView : public OptionsPageView, + public ChromeViews::NativeButton::Listener, + public ChromeViews::TableViewObserver, + public ChromeViews::ComboBox::Listener { + public: + explicit LanguagesPageView(Profile* profile); + virtual ~LanguagesPageView(); + + // ChromeViews::NativeButton::Listener implementation: + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // Save Changes made to relevant pref members associated with this tab. + // This is public since it is called by FontsLanguageWindowView in its + // Dialog Delegate Accept() method. + void SaveChanges(); + + // This is public because when user clicks OK in AddLanguageView dialog, + // this is called back in the LanguagePageView delegate in order to add + // this language to the table model in this tab. + void OnAddLanguage(const std::wstring& new_language); + + protected: + // OptionsPageView implementation: + virtual void InitControlLayout(); + virtual void NotifyPrefChanged(const std::wstring* pref_name); + + // ChromeViews::ComboBox::Listener implementation: + virtual void ItemChanged(ChromeViews::ComboBox* sender, + int prev_index, + int new_index); + + private: + // Invoked when the selection of the table view changes. Updates the enabled + // property of the remove button. + virtual void OnSelectionChanged(); + void OnRemoveLanguage(); + void OnMoveDownLanguage(); + void OnMoveUpLanguage(); + + ChromeViews::Label* languages_instructions_; + ChromeViews::View* languages_contents_; + ChromeViews::View* button_stack_; + ChromeViews::TableView* language_order_table_; + ChromeViews::NativeButton* move_up_button_; + ChromeViews::NativeButton* move_down_button_; + ChromeViews::NativeButton* add_button_; + ChromeViews::NativeButton* remove_button_; + ChromeViews::Label* language_info_label_; + ChromeViews::Label* ui_language_label_; + ChromeViews::ComboBox* change_ui_language_combobox_; + ChromeViews::ComboBox* change_dictionary_language_combobox_; + ChromeViews::Label* dictionary_language_label_; + + scoped_ptr<LanguageOrderTableModel> language_order_table_model_; + AddLanguageView* add_language_instance_; + StringPrefMember accept_languages_; + + // The contents of the "user interface language" combobox. + scoped_ptr<LanguageComboboxModel> ui_language_model_; + StringPrefMember app_locale_; + + // The contents of the "dictionary language" combobox. + scoped_ptr<LanguageComboboxModel> dictionary_language_model_; + StringPrefMember dictionary_language_; + + bool language_table_edited_; + + DISALLOW_EVIL_CONSTRUCTORS(LanguagesPageView); +}; + +#endif // CHROME_BROWSER_VIEWS_OPTIONS_LANGUAGES_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/options_group_view.cc b/chrome/browser/views/options/options_group_view.cc new file mode 100644 index 0000000..80eb0bb --- /dev/null +++ b/chrome/browser/views/options/options_group_view.cc @@ -0,0 +1,161 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <vsstyle.h> +#include <vssym32.h> + +#include "chrome/browser/views/options/options_group_view.h" + +#include "base/gfx/native_theme.h" +#include "base/gfx/skia_utils.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/separator.h" +#include "generated_resources.h" + +static const int kLeftColumnWidthChars = 20; +static const int kOptionsGroupViewColumnSpacing = 30; + +/////////////////////////////////////////////////////////////////////////////// +// OptionsGroupView, public: + +OptionsGroupView::OptionsGroupView(ChromeViews::View* contents, + const std::wstring& title, + const std::wstring& description, + bool show_separator) + : contents_(contents), + title_label_(new ChromeViews::Label(title)), + description_label_(new ChromeViews::Label(description)), + separator_(NULL), + show_separator_(show_separator), + highlighted_(false) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont title_font = + rb.GetFont(ResourceBundle::BaseFont).DeriveFont(0, ChromeFont::BOLD); + title_label_->SetFont(title_font); + SkColor title_color = gfx::NativeTheme::instance()->GetThemeColorWithDefault( + gfx::NativeTheme::BUTTON, BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, COLOR_WINDOWTEXT); + title_label_->SetColor(title_color); + title_label_->SetMultiLine(true); + title_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + + description_label_->SetMultiLine(true); + description_label_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); +} + +void OptionsGroupView::SetHighlighted(bool highlighted) { + highlighted_ = highlighted; + SchedulePaint(); +} + +int OptionsGroupView::GetContentsWidth() const { + return contents_->GetWidth(); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsGroupView, ChromeViews::View overrides: + +void OptionsGroupView::Paint(ChromeCanvas* canvas) { + if (highlighted_) { + COLORREF infocolor = GetSysColor(COLOR_INFOBK); + SkColor background_color = SkColorSetRGB(GetRValue(infocolor), + GetGValue(infocolor), + GetBValue(infocolor)); + int y_offset = kUnrelatedControlVerticalSpacing / 2; + canvas->FillRectInt(background_color, 0, 0, GetWidth(), + GetHeight() - y_offset); + } +} + +void OptionsGroupView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (is_add && child == this) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsGroupView, private: + +void OptionsGroupView::Init() { + using ChromeViews::GridLayout; + using ChromeViews::ColumnSet; + + GridLayout* layout = new GridLayout(this); + SetLayoutManager(layout); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + std::wstring left_column_chars = + l10n_util::GetString(IDS_OPTIONS_DIALOG_LEFT_COLUMN_WIDTH_CHARS); + int left_column_width = + font.ave_char_width() * _wtoi(left_column_chars.c_str()); + + const int two_column_layout_id = 0; + ColumnSet* column_set = layout->AddColumnSet(two_column_layout_id); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, + GridLayout::FIXED, left_column_width, 0); + column_set->AddPaddingColumn(0, kOptionsGroupViewColumnSpacing); + column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 1, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(0, two_column_layout_id); + // We need to do this so that the label can calculate an appropriate + // preferred size (width * height) based on this width constraint. Otherwise + // Label::GetPreferredSize will return 0,0 as the preferred size. + title_label_->SetBounds(0, 0, left_column_width, 0); + layout->AddView(title_label_); + layout->AddView(contents_, 1, 3, GridLayout::FILL, GridLayout::FILL); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(1, two_column_layout_id); + // See comment above for title_label_. + description_label_->SetBounds(0, 0, left_column_width, 0); + layout->AddView(description_label_); + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + if (show_separator_) { + const int single_column_layout_id = 1; + column_set = layout->AddColumnSet(single_column_layout_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + separator_ = new ChromeViews::Separator; + layout->StartRow(0, single_column_layout_id); + layout->AddView(separator_); + } +} diff --git a/chrome/browser/views/options/options_group_view.h b/chrome/browser/views/options/options_group_view.h new file mode 100644 index 0000000..b6c0e3f --- /dev/null +++ b/chrome/browser/views/options/options_group_view.h @@ -0,0 +1,100 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_GROUP_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_GROUP_VIEW_H__ + +#include "chrome/views/view.h" + +namespace ChromeViews { +class Label; +class Separator; +}; + +/////////////////////////////////////////////////////////////////////////////// +// OptionsGroupView +// +// A helper View that gathers related options into groups with a title and +// optional description. +// +class OptionsGroupView : public ChromeViews::View { + public: + OptionsGroupView(ChromeViews::View* contents, + const std::wstring& title, + const std::wstring& description, + bool show_separator); + virtual ~OptionsGroupView() {} + + // Sets the group as being highlighted to attract attention. + void SetHighlighted(bool highlighted); + + // Retrieves the width of the ContentsView. Used to help size wrapping items. + int GetContentsWidth() const; + + class ContentsView : public ChromeViews::View { + public: + virtual ~ContentsView() {} + + // ChromeViews::View overrides: + virtual void DidChangeBounds(const CRect& prev_bounds, + const CRect& next_bounds) { + Layout(); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ContentsView); + }; + + protected: + // ChromeViews::View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + private: + void Init(); + + ChromeViews::View* contents_; + ChromeViews::Label* title_label_; + ChromeViews::Label* description_label_; + ChromeViews::Separator* separator_; + + // True if we should show a separator line below the contents of this + // section. + bool show_separator_; + + // True if this section should have a highlighted treatment to draw the + // user's attention. + bool highlighted_; + + DISALLOW_EVIL_CONSTRUCTORS(OptionsGroupView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_GROUP_VIEW_H__ diff --git a/chrome/browser/views/options/options_page_view.cc b/chrome/browser/views/options/options_page_view.cc new file mode 100644 index 0000000..b659249 --- /dev/null +++ b/chrome/browser/views/options/options_page_view.cc @@ -0,0 +1,82 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/options/options_page_view.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/common/pref_service.h" + +/////////////////////////////////////////////////////////////////////////////// +// OptionsPageView + +OptionsPageView::OptionsPageView(Profile* profile) + : profile_(profile), + initialized_(false) { +} + +OptionsPageView::~OptionsPageView() { +} + +void OptionsPageView::UserMetricsRecordAction(const wchar_t* action, + PrefService* prefs) { + UserMetrics::RecordComputedAction(action, profile()); + if (prefs) + prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsPageView, NotificationObserver implementation: + +void OptionsPageView::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NOTIFY_PREF_CHANGED) + NotifyPrefChanged(Details<std::wstring>(details).ptr()); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsPageView, ChromeViews::View overrides: + +void OptionsPageView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (!initialized_ && is_add && GetViewContainer()) { + // It is important that this only get done _once_ otherwise we end up + // duplicating the view hierarchy when tabs are switched. + initialized_ = true; + InitControlLayout(); + NotifyPrefChanged(NULL); + } +} + +HWND OptionsPageView::GetRootWindow() const { + // Our ViewContainer is the TabbedPane content HWND, which is a child HWND. + // We need the root HWND for parenting. + return GetAncestor(GetViewContainer()->GetHWND(), GA_ROOT); +}
\ No newline at end of file diff --git a/chrome/browser/views/options/options_page_view.h b/chrome/browser/views/options/options_page_view.h new file mode 100644 index 0000000..cf107e5 --- /dev/null +++ b/chrome/browser/views/options/options_page_view.h @@ -0,0 +1,104 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_PAGE_VIEW_H__ +#define CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_PAGE_VIEW_H__ + +#include "chrome/browser/options_window.h" +#include "chrome/browser/profile.h" +#include "chrome/common/notification_service.h" +#include "chrome/views/link.h" +#include "chrome/views/native_button.h" + +class PrefService; + +/////////////////////////////////////////////////////////////////////////////// +// OptionsPageView +// +// A base class for Options dialog pages that handles ensuring control +// initialization is done just once. +// +class OptionsPageView : public ChromeViews::View, + public NotificationObserver { + public: + virtual ~OptionsPageView(); + + // Returns true if the window containing this view can be closed, given the + // current state of this view. This can be used to prevent the window from + // being closed when a modal dialog box is showing, for example. + virtual bool CanClose() const { return true; } + + // Highlights the specified group to attract the user's attention. + virtual void HighlightGroup(OptionsGroup highlight_group) { } + + // Overridden from NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + protected: + // This class cannot be instantiated directly, but its constructor must be + // called by derived classes. + explicit OptionsPageView(Profile* profile); + + // Returns the Profile associated with this page. + Profile* profile() const { return profile_; } + + // Records a user action and schedules the prefs file to be saved. + void UserMetricsRecordAction(const wchar_t* action, PrefService* prefs); + + // Initializes the layout of the controls within the panel. + virtual void InitControlLayout() = 0; + + // Allows the UI to update when a preference value changes. The parameter is + // the specific pref that changed, or NULL if all pref UI should be + // validated. This is also called immediately after InitControlLayout() + // during setup, but with NULL as the parameter to allow initial state to be + // set. + virtual void NotifyPrefChanged(const std::wstring* pref_name) { } + + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + + // Returns the HWND on which created windows should be parented. + HWND GetRootWindow() const; + + private: + // The Profile associated with this page. + Profile* profile_; + + // Whether or not the control layout has been initialized for this page. + bool initialized_; + + DISALLOW_EVIL_CONSTRUCTORS(OptionsPageView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_OPTIONS_OPTIONS_PAGE_VIEW_H__ diff --git a/chrome/browser/views/options/options_window_view.cc b/chrome/browser/views/options/options_window_view.cc new file mode 100644 index 0000000..72c7029 --- /dev/null +++ b/chrome/browser/views/options/options_window_view.cc @@ -0,0 +1,254 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/options_window.h" + +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/options/advanced_page_view.h" +#include "chrome/browser/views/options/content_page_view.h" +#include "chrome/browser/views/options/general_page_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/tabbed_pane.h" +#include "chrome/views/window.h" +#include "generated_resources.h" + +static const int kDefaultWindowWidthChars = 85; +static const int kDefaultWindowHeightLines = 29; + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView +// +// The contents of the Options dialog window. +// +class OptionsWindowView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::TabbedPane::Listener { + public: + explicit OptionsWindowView(Profile* profile); + virtual ~OptionsWindowView(); + + ChromeViews::Window* container() const { return container_; } + void set_container(ChromeViews::Window* container) { + container_ = container; + } + + // Shows the Tab corresponding to the specified OptionsPage. + void ShowOptionsPage(OptionsPage page, OptionsGroup highlight_group); + + // ChromeViews::DialogDelegate implementation: + virtual int GetDialogButtons() const { return DIALOGBUTTON_CANCEL; } + virtual std::wstring GetWindowTitle() const; + virtual void WindowClosing(); + virtual bool Cancel(); + + // ChromeViews::TabbedPane::Listener implementation: + virtual void TabSelectedAt(int index); + + // ChromeViews::View overrides: + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + + protected: + // ChromeViews::View overrides: + virtual void ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child); + private: + // Init the assorted Tabbed pages + void Init(); + + // Returns the currently selected OptionsPageView. + OptionsPageView* GetCurrentOptionsPageView() const; + + // The Tab view that contains all of the options pages. + ChromeViews::TabbedPane* tabs_; + + // The Options dialog window. + ChromeViews::Window* container_; + + // The Profile associated with these options. + Profile* profile_; + + // The last page the user was on when they opened the Options window. + IntegerPrefMember last_selected_page_; + + DISALLOW_EVIL_CONSTRUCTORS(OptionsWindowView); +}; + +// static +static OptionsWindowView* instance_ = NULL; +static const int kDialogPadding = 7; + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView, public: + +OptionsWindowView::OptionsWindowView(Profile* profile) + // Always show preferences for the original profile. Most state when off + // the record comes from the original profile, but we explicitly use + // the original profile to avoid potential problems. + : profile_(profile->GetOriginalProfile()) { + // The download manager needs to be initialized before the contents of the + // Options Window are created. + profile_->GetDownloadManager(); + // We don't need to observe changes in this value. + last_selected_page_.Init(prefs::kOptionsWindowLastTabIndex, + g_browser_process->local_state(), NULL); +} + +OptionsWindowView::~OptionsWindowView() { +} + +void OptionsWindowView::ShowOptionsPage(OptionsPage page, + OptionsGroup highlight_group) { + // If the window is not yet visible, we need to show it (it will become + // active), otherwise just bring it to the front. + if (!container_->IsVisible()) { + container_->Show(); + } else { + container_->Activate(); + } + + if (page == OPTIONS_PAGE_DEFAULT) { + // Remember the last visited page from local state. + page = static_cast<OptionsPage>(last_selected_page_.GetValue()); + if (page == OPTIONS_PAGE_DEFAULT) + page = OPTIONS_PAGE_GENERAL; + } + DCHECK(page > OPTIONS_PAGE_DEFAULT && page < OPTIONS_PAGE_COUNT); + tabs_->SelectTabAt(static_cast<int>(page)); + + GetCurrentOptionsPageView()->HighlightGroup(highlight_group); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView, ChromeViews::DialogDelegate implementation: + +std::wstring OptionsWindowView::GetWindowTitle() const { + return l10n_util::GetStringF(IDS_OPTIONS_DIALOG_TITLE, + l10n_util::GetString(IDS_PRODUCT_NAME)); +} + +void OptionsWindowView::WindowClosing() { + // Clear the static instance so that the next time ShowOptionsWindow() is + // called a new window is opened. + instance_ = NULL; +} + +bool OptionsWindowView::Cancel() { + return GetCurrentOptionsPageView()->CanClose(); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView, ChromeViews::TabbedPane::Listener implementation: + +void OptionsWindowView::TabSelectedAt(int index) { + DCHECK(index > OPTIONS_PAGE_DEFAULT && index < OPTIONS_PAGE_COUNT); + last_selected_page_.SetValue(index); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView, ChromeViews::View overrides: + +void OptionsWindowView::Layout() { + tabs_->SetBounds(kDialogPadding, kDialogPadding, + GetWidth() - (2 * kDialogPadding), + GetHeight() - (2 * kDialogPadding)); +} + +void OptionsWindowView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_OPTIONS_DIALOG_WIDTH_CHARS, + IDS_OPTIONS_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +void OptionsWindowView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + // Can't init before we're inserted into a ViewContainer, because we require + // a HWND to parent native child controls to. + if (is_add && child == this) + Init(); +} + +/////////////////////////////////////////////////////////////////////////////// +// OptionsWindowView, private: + +void OptionsWindowView::Init() { + tabs_ = new ChromeViews::TabbedPane; + tabs_->SetListener(this); + AddChildView(tabs_); + + GeneralPageView* general_page = new GeneralPageView(profile_); + tabs_->AddTabAtIndex(0, l10n_util::GetString(IDS_OPTIONS_GENERAL_TAB_LABEL), + general_page, false); + + ContentPageView* content_page = new ContentPageView(profile_); + tabs_->AddTabAtIndex(1, l10n_util::GetString(IDS_OPTIONS_CONTENT_TAB_LABEL), + content_page, false); + + AdvancedPageView* advanced_page = new AdvancedPageView(profile_); + tabs_->AddTabAtIndex(2, l10n_util::GetString(IDS_OPTIONS_ADVANCED_TAB_LABEL), + advanced_page, false); + + DCHECK(tabs_->GetTabCount() == OPTIONS_PAGE_COUNT); +} + +OptionsPageView* OptionsWindowView::GetCurrentOptionsPageView() const { + ChromeViews::RootView* contents_root_view = tabs_->GetContentsRootView(); + DCHECK(contents_root_view->GetChildViewCount() == 1); + return static_cast<OptionsPageView*>(contents_root_view->GetChildViewAt(0)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Factory/finder method: + +void ShowOptionsWindow(OptionsPage page, + OptionsGroup highlight_group, + Profile* profile) { + DCHECK(profile); + // If there's already an existing options window, activate it and switch to + // the specified page. + // TODO(beng): note this is not multi-simultaneous-profile-safe. When we care + // about this case this will have to be fixed. + if (!instance_) { + instance_ = new OptionsWindowView(profile); + instance_->set_container(ChromeViews::Window::CreateChromeWindow( + NULL, gfx::Rect(), instance_, instance_)); + // The window is alive by itself now... + } + instance_->ShowOptionsPage(page, highlight_group); +} diff --git a/chrome/browser/views/password_manager_view.cc b/chrome/browser/views/password_manager_view.cc new file mode 100644 index 0000000..cf5eb50 --- /dev/null +++ b/chrome/browser/views/password_manager_view.cc @@ -0,0 +1,399 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_util.h" +#include "chrome/common/l10n_util.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/views/password_manager_view.h" +#include "chrome/views/background.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/native_button.h" +#include "webkit/glue/password_form.h" + +#include "generated_resources.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; + +// We can only have one PasswordManagerView at a time. +static PasswordManagerView* instance_ = NULL; + +static const int kDefaultWindowWidth = 530; +static const int kDefaultWindowHeight = 240; + +//////////////////////////////////////////////////////////////////////////////// +// MultiLabelButtons +// +MultiLabelButtons::MultiLabelButtons(const std::wstring& label, + const std::wstring& alt_label) + : NativeButton(label), + label_(label), + alt_label_(alt_label), + pref_size_(-1, -1) { +} + +void MultiLabelButtons::GetPreferredSize(CSize *out) { + if (pref_size_.cx == -1 && pref_size_.cy == -1) { + // Let's compute our preferred size. + std::wstring current_label = GetLabel(); + SetLabel(label_); + NativeButton::GetPreferredSize(&pref_size_); + SetLabel(alt_label_); + CSize alt_pref_size; + NativeButton::GetPreferredSize(&alt_pref_size); + // Revert to the original label. + SetLabel(current_label); + pref_size_.cx = std::max(pref_size_.cx, alt_pref_size.cx); + pref_size_.cy = std::max(pref_size_.cy, alt_pref_size.cy); + } + *out = pref_size_; +} + +//////////////////////////////////////////////////////////////////// +// PasswordManagerTableModel +PasswordManagerTableModel::PasswordManagerTableModel( + WebDataService* profile_web_data_service) + : saved_signons_deleter_(&saved_signons_), + observer_(NULL), + pending_login_query_(NULL), + web_data_service_(profile_web_data_service) { + DCHECK(web_data_service_); +} + +PasswordManagerTableModel::~PasswordManagerTableModel() { + CancelLoginsQuery(); +} + +int PasswordManagerTableModel::RowCount() { + return static_cast<int>(saved_signons_.size()); +} + +std::wstring PasswordManagerTableModel::GetText(int row, + int col_id) { + PasswordForm* signon = GetPasswordFormAt(row); + DCHECK(signon); + switch (col_id) { + case IDS_PASSWORD_MANAGER_VIEW_SITE_COLUMN: // Site. + return UTF8ToWide(signon->origin.spec()); + case IDS_PASSWORD_MANAGER_VIEW_USERNAME_COLUMN: // Username. + return signon->username_value; + default: + NOTREACHED() << "Invalid column."; + return std::wstring(); + } +} + +void PasswordManagerTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +void PasswordManagerTableModel::GetAllSavedLoginsForProfile() { + DCHECK(!pending_login_query_); + pending_login_query_ = web_data_service_->GetAllAutofillableLogins(this); +} + +void PasswordManagerTableModel::OnWebDataServiceRequestDone( + WebDataService::Handle h, + const WDTypedResult* result) { + DCHECK_EQ(pending_login_query_, h); + pending_login_query_ = NULL; + + if (!result) + return; + + DCHECK(result->GetType() == PASSWORD_RESULT); + + // Get the result from the database into a useable form. + const WDResult<std::vector<PasswordForm*> >* r = + static_cast<const WDResult<std::vector<PasswordForm*> >*>(result); + // Copy vector of *pointers* to saved_logins_, thus taking ownership of the + // PasswordForm's in the result. + saved_signons_ = r->GetValue(); + if (observer_) + observer_->OnModelChanged(); +} + +void PasswordManagerTableModel::CancelLoginsQuery() { + if (pending_login_query_) { + web_data_service_->CancelRequest(pending_login_query_); + pending_login_query_ = NULL; + } +} + +PasswordForm* PasswordManagerTableModel::GetPasswordFormAt(int row) { + DCHECK(row >= 0 && row < RowCount()); + return saved_signons_[row]; +} + +void PasswordManagerTableModel::ForgetAndRemoveSignon(int row) { + DCHECK(row >= 0 && row < RowCount()); + PasswordForms::iterator target_iter = saved_signons_.begin() + row; + // Remove from DB, memory, and vector. + web_data_service_->RemoveLogin(**target_iter); + delete *target_iter; + saved_signons_.erase(target_iter); + if (observer_) + observer_->OnItemsRemoved(row, 1); +} + +void PasswordManagerTableModel::ForgetAndRemoveAllSignons() { + PasswordForms::iterator iter = saved_signons_.begin(); + while (iter != saved_signons_.end()) { + web_data_service_->RemoveLogin(**iter); + delete *iter; + iter = saved_signons_.erase(iter); + } + if (observer_) + observer_->OnModelChanged(); +} + +////////////////////////////////////////////////////////////////////// +// PasswordManagerView + +// static +void PasswordManagerView::Show(Profile* profile) { + DCHECK(profile); + if (!instance_) { + instance_ = new PasswordManagerView(profile); + + // manager is owned by the dialog window, so Close() will delete it. + instance_->dialog_ = ChromeViews::Window::CreateChromeWindow(NULL, + gfx::Rect(), + instance_, + instance_); + } + if (!instance_->dialog_->IsVisible()) { + instance_->dialog_->Show(); + } else { + instance_->dialog_->Activate(); + } +} + +PasswordManagerView::PasswordManagerView(Profile* profile) + : show_button_( + l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_SHOW_BUTTON), + l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_HIDE_BUTTON)), + remove_button_(l10n_util::GetString( + IDS_PASSWORD_MANAGER_VIEW_REMOVE_BUTTON)), + remove_all_button_(l10n_util::GetString( + IDS_PASSWORD_MANAGER_VIEW_REMOVE_ALL_BUTTON)), + table_model_(profile->GetWebDataService(Profile::EXPLICIT_ACCESS)) { + Init(); +} + +void PasswordManagerView::SetupTable() { + // Creates the different columns for the table. + // The float resize values are the result of much tinkering. + std::vector<ChromeViews::TableColumn> columns; + columns.push_back( + ChromeViews::TableColumn( + IDS_PASSWORD_MANAGER_VIEW_SITE_COLUMN, + ChromeViews::TableColumn::LEFT, -1, 0.55f)); + columns.push_back( + ChromeViews::TableColumn( + IDS_PASSWORD_MANAGER_VIEW_USERNAME_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0.37f)); + table_view_ = new ChromeViews::TableView(&table_model_, + columns, + ChromeViews::TEXT_ONLY, + true, true, true); + table_view_->SetObserver(this); +} + +void PasswordManagerView::SetupButtonsAndLabels() { + // Tell View not to delete class stack allocated views. + + show_button_.SetParentOwned(false); + show_button_.SetListener(this); + show_button_.SetEnabled(false); + + remove_button_.SetParentOwned(false); + remove_button_.SetListener(this); + remove_button_.SetEnabled(false); + + remove_all_button_.SetParentOwned(false); + remove_all_button_.SetListener(this); + + password_label_.SetParentOwned(false); +} + +void PasswordManagerView::Init() { + // Configure the background and view elements (buttons, labels, table). + SetupButtonsAndLabels(); + SetupTable(); + + // Do the layout thing. + const int top_column_set_id = 0; + const int lower_column_set_id = 1; + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + // Design the grid. + ColumnSet* column_set = layout->AddColumnSet(top_column_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, 300, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + + column_set = layout->AddColumnSet(lower_column_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(1, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::LEADING, 0, + GridLayout::USE_PREF, 0, 0); + column_set->LinkColumnSizes(0, 2, -1); + + // Fill the grid. + layout->StartRow(0.05f, top_column_set_id); + layout->AddView(table_view_, 1, 3); + layout->AddView(&remove_button_); + layout->StartRow(0.05f, top_column_set_id); + layout->SkipColumns(1); + layout->AddView(&show_button_); + layout->StartRow(0.80f, top_column_set_id); + layout->SkipColumns(1); + layout->AddView(&password_label_); + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + // Ask the database for saved password data. + table_model_.GetAllSavedLoginsForProfile(); +} + +PasswordManagerView::~PasswordManagerView() { +} + +void PasswordManagerView::Layout() { + GetLayoutManager()->Layout(this); + + // Manually lay out the Remove All button in the same row as + // the close button. + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + CSize prefsize; + remove_all_button_.GetPreferredSize(&prefsize); + int button_y = parent_bounds.bottom - prefsize.cy - kButtonVEdgeMargin; + remove_all_button_.SetBounds(kPanelHorizMargin, button_y, prefsize.cx, + prefsize.cy); +} + +void PasswordManagerView::GetPreferredSize(CSize* out) { + out->cx = kDefaultWindowWidth; + out->cy = kDefaultWindowHeight; +} + +void PasswordManagerView::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + if (child == this) { + // Add and remove the Remove All button from the ClientView's hierarchy. + if (is_add) { + parent->AddChildView(&remove_all_button_); + } else { + parent->RemoveChildView(&remove_all_button_); + } + } +} + +void PasswordManagerView::OnSelectionChanged() { + bool has_selection = table_view_->SelectedRowCount() > 0; + remove_button_.SetEnabled(has_selection); + // Reset the password related views. + show_button_.SetLabel( + l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_SHOW_BUTTON)); + show_button_.SetEnabled(has_selection); + password_label_.SetText(std::wstring()); +} + +int PasswordManagerView::GetDialogButtons() const { + return DIALOGBUTTON_CANCEL; +} + +bool PasswordManagerView::CanResize() const { + return true; +} + +bool PasswordManagerView::CanMaximize() const { + return false; +} + +bool PasswordManagerView::IsAlwaysOnTop() const { + return false; +} + +bool PasswordManagerView::HasAlwaysOnTopMenu() const { + return false; +} + +std::wstring PasswordManagerView::GetWindowTitle() const { + return l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_TITLE); +} + +void PasswordManagerView::ButtonPressed(ChromeViews::NativeButton* sender) { + DCHECK(dialog_); + // Close will result in our destruction. + if (sender == &remove_all_button_) { + table_model_.ForgetAndRemoveAllSignons(); + return; + } + + // The following require a selection (and only one, since table is single- + // select only). + ChromeViews::TableSelectionIterator iter = table_view_->SelectionBegin(); + int row = *iter; + PasswordForm* selected = table_model_.GetPasswordFormAt(row); + DCHECK(++iter == table_view_->SelectionEnd()); + + if (sender == &remove_button_) { + table_model_.ForgetAndRemoveSignon(row); + } else if (sender == &show_button_) { + if (password_label_.GetText().length() == 0) { + password_label_.SetText(selected->password_value); + show_button_.SetLabel( + l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_HIDE_BUTTON)); + } else { + password_label_.SetText(L""); + show_button_.SetLabel( + l10n_util::GetString(IDS_PASSWORD_MANAGER_VIEW_SHOW_BUTTON)); + } + } else { + NOTREACHED() << "Invalid button."; + } +} + +void PasswordManagerView::WindowClosing() { + // The table model will be deleted before the table view, so detach it. + table_view_->SetModel(NULL); + + // Clear the static instance so the next time Show() is called, a new + // instance is created. + instance_ = NULL; +} diff --git a/chrome/browser/views/password_manager_view.h b/chrome/browser/views/password_manager_view.h new file mode 100644 index 0000000..19c92ad --- /dev/null +++ b/chrome/browser/views/password_manager_view.h @@ -0,0 +1,170 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_VIEW_H__ +#define CHROME_BROWSER_PASSWORD_MANAGER_VIEW_H__ + +#include <vector> + +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/label.h" +#include "chrome/views/native_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/window.h" + +class Profile; +struct PasswordForm; + +class PasswordManagerTableModel : public ChromeViews::TableModel, + public WebDataServiceConsumer { + public: + explicit PasswordManagerTableModel(WebDataService* profile_web_data_service); + virtual ~PasswordManagerTableModel(); + + // TableModel methods. + virtual int RowCount(); + virtual std::wstring GetText(int row, int column); + virtual void SetObserver(ChromeViews::TableModelObserver* observer); + + // Delete the PasswordForm at specified row from the database (and remove + // from view). + void ForgetAndRemoveSignon(int row); + + // Delete all saved signons for the active profile (via web data service), + // and clear the view. + void ForgetAndRemoveAllSignons(); + + // WebDataServiceConsumer implementation. + virtual void OnWebDataServiceRequestDone(WebDataService::Handle h, + const WDTypedResult* result); + // Request saved logins data. + void GetAllSavedLoginsForProfile(); + + // Return the PasswordForm at the specified index. + PasswordForm* GetPasswordFormAt(int row); + + private: + // Cancel any pending login query involving a callback. + void CancelLoginsQuery(); + + // The TableView observing this model. + ChromeViews::TableModelObserver* observer_; + + // Handle to any pending WebDataService::GetLogins query. + WebDataService::Handle pending_login_query_; + + // PasswordForms returned by the web data service query. + typedef std::vector<PasswordForm*> PasswordForms; + PasswordForms saved_signons_; + + // Deleter for saved_logins_. + STLElementDeleter<PasswordForms> saved_signons_deleter_; + + // The web data service associated with the currently active profile. + WebDataService* web_data_service_; + + DISALLOW_EVIL_CONSTRUCTORS(PasswordManagerTableModel); +}; + +// A button that can have 2 different labels set on it and for which the +// preferred size is the size of the widest string. +class MultiLabelButtons : public ChromeViews::NativeButton { + public: + MultiLabelButtons(const std::wstring& label, const std::wstring& alt_label); + + virtual void GetPreferredSize(CSize *out); + + private: + std::wstring label_; + std::wstring alt_label_; + CSize pref_size_; + + DISALLOW_EVIL_CONSTRUCTORS(MultiLabelButtons); +}; + +class PasswordManagerView : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::TableViewObserver, + public ChromeViews::NativeButton::Listener { + public: + explicit PasswordManagerView(Profile* profile); + virtual ~PasswordManagerView(); + + // Show the PasswordManagerContentView for the given profile. + static void Show(Profile* profile); + + // View methods. + virtual void Layout(); + virtual void GetPreferredSize(CSize *out); + virtual void ViewHierarchyChanged(bool is_add, ChromeViews::View* parent, + ChromeViews::View* child); + + // ChromeViews::TableViewObserver implementation. + virtual void OnSelectionChanged(); + + // NativeButton::Listener implementation. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ChromeViews::DialogDelegate methods: + virtual int GetDialogButtons() const; + virtual bool CanResize() const; + virtual bool CanMaximize() const; + virtual bool IsAlwaysOnTop() const; + virtual bool HasAlwaysOnTopMenu() const; + virtual std::wstring GetWindowTitle() const; + virtual void WindowClosing(); + + private: + // Wire up buttons, the model, and the table view, and query the DB for + // saved login data tied to the given profile. + void Init(); + + // Helper to configure our buttons and labels. + void SetupButtonsAndLabels(); + + // Helper to configure our table view. + void SetupTable(); + + // Components in this view. + PasswordManagerTableModel table_model_; + ChromeViews::TableView* table_view_; + + // The buttons and labels. + MultiLabelButtons show_button_; + ChromeViews::NativeButton remove_button_; + ChromeViews::NativeButton remove_all_button_; + ChromeViews::Label password_label_; + + // The window containing this view. + ChromeViews::Window* dialog_; + + DISALLOW_EVIL_CONSTRUCTORS(PasswordManagerView); +}; +#endif // CHROME_BROWSER_PASSWORD_MANAGER_VIEW_H__ diff --git a/chrome/browser/views/restart_message_box.cc b/chrome/browser/views/restart_message_box.cc new file mode 100644 index 0000000..3d940ab --- /dev/null +++ b/chrome/browser/views/restart_message_box.cc @@ -0,0 +1,84 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/restart_message_box.h" + +#include "chrome/views/message_box_view.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +//////////////////////////////////////////////////////////////////////////////// +// RestartMessageBox, public: + +// static +void RestartMessageBox::ShowMessageBox(HWND parent_hwnd) { + // When the window closes, it will delete itself. + new RestartMessageBox(parent_hwnd); +} + +int RestartMessageBox::GetDialogButtons() const { + return DialogDelegate::DIALOGBUTTON_OK; +} + +std::wstring RestartMessageBox::GetDialogButtonLabel(DialogButton button) + const { + DCHECK(button == DIALOGBUTTON_OK); + return l10n_util::GetString(IDS_OK); +} + +std::wstring RestartMessageBox::GetWindowTitle() const { + return l10n_util::GetString(IDS_PRODUCT_NAME); +} + +void RestartMessageBox::WindowClosing() { + delete this; +} + +bool RestartMessageBox::IsModal() const { + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// RestartMessageBox, private: + +RestartMessageBox::RestartMessageBox(HWND parent_hwnd) { + const int kDialogWidth = 400; + // Also deleted when the window closes. + MessageBoxView* message_box_view = new MessageBoxView( + MessageBoxView::kFlagHasMessage | MessageBoxView::kFlagHasOKButton, + l10n_util::GetString(IDS_OPTIONS_RESTART_REQUIRED).c_str(), + std::wstring(), + kDialogWidth); + ChromeViews::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), + message_box_view, this)->Show(); +} + +RestartMessageBox::~RestartMessageBox() { +} diff --git a/chrome/browser/views/restart_message_box.h b/chrome/browser/views/restart_message_box.h new file mode 100644 index 0000000..5a5da45 --- /dev/null +++ b/chrome/browser/views/restart_message_box.h @@ -0,0 +1,60 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_RESTART_MESSAGE_BOX_H_ +#define CHROME_BROWSER_VIEWS_RESTART_MESSAGE_BOX_H_ + +#include "base/basictypes.h" +#include "chrome/views/dialog_delegate.h" + +// A dialog box that tells the user that s/he needs to restart Chrome +// for a change to take effect. +class RestartMessageBox : public ChromeViews::DialogDelegate { + public: + // This box is modal to |parent_hwnd|. + static void ShowMessageBox(HWND parent_hwnd); + + protected: + // ChromeViews::DialogDelegate: + virtual int GetDialogButtons() const; + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual std::wstring GetWindowTitle() const; + + // ChromeViews::WindowDelegate: + virtual void WindowClosing(); + virtual bool IsModal() const; + + private: + explicit RestartMessageBox(HWND parent_hwnd); + virtual ~RestartMessageBox(); + + DISALLOW_EVIL_CONSTRUCTORS(RestartMessageBox); +}; + +#endif // CHROME_BROWSER_VIEWS_RESTART_MESSAGE_BOX_H_ diff --git a/chrome/browser/views/sad_tab_view.cc b/chrome/browser/views/sad_tab_view.cc new file mode 100644 index 0000000..9800e0b --- /dev/null +++ b/chrome/browser/views/sad_tab_view.cc @@ -0,0 +1,137 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/sad_tab_view.h" + +#include "base/gfx/size.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "generated_resources.h" +#include "skia/include/SkGradientShader.h" + +static const int kSadTabOffset = -64; +static const int kIconTitleSpacing = 20; +static const int kTitleMessageSpacing = 15; +static const int kMessageBottomMargin = 20; +static const float kMessageSize = 0.65f; +static const SkColor kTitleColor = SK_ColorWHITE; +static const SkColor kMessageColor = SK_ColorWHITE; +static const SkColor kBackgroundColor = SkColorSetRGB(35, 48, 64); +static const SkColor kBackgroundEndColor = SkColorSetRGB(35, 48, 64); + +// static +SkBitmap* SadTabView::sad_tab_bitmap_ = NULL; +ChromeFont SadTabView::title_font_; +ChromeFont SadTabView::message_font_; +std::wstring SadTabView::title_; +std::wstring SadTabView::message_; +int SadTabView::title_width_; + +SadTabView::SadTabView() { + InitClass(); +} + +static SkShader* CreateGradientShader(int end_point) { + SkColor grad_colors[2] = { kBackgroundColor, kBackgroundEndColor }; + SkPoint grad_points[2]; + grad_points[0].set(SkIntToScalar(0), SkIntToScalar(0)); + grad_points[1].set(SkIntToScalar(0), SkIntToScalar(end_point)); + return SkGradientShader::CreateLinear( + grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode); +} + +void SadTabView::Paint(ChromeCanvas* canvas) { + SkShader* background_shader = CreateGradientShader(GetHeight()); + SkPaint paint; + paint.setShader(background_shader); + background_shader->unref(); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawRectCoords(0, 0, + SkIntToScalar(GetWidth()), SkIntToScalar(GetHeight()), + paint); + + canvas->DrawBitmapInt(*sad_tab_bitmap_, icon_bounds_.x(), icon_bounds_.y()); + + canvas->DrawStringInt(title_, title_font_, kTitleColor, title_bounds_.x(), + title_bounds_.y(), title_bounds_.width(), + title_bounds_.height(), + ChromeCanvas::TEXT_ALIGN_CENTER); + + canvas->DrawStringInt(message_, message_font_, kMessageColor, + message_bounds_.x(), message_bounds_.y(), + message_bounds_.width(), message_bounds_.height(), + ChromeCanvas::MULTI_LINE); +} + +void SadTabView::Layout() { + int icon_width = sad_tab_bitmap_->width(); + int icon_height = sad_tab_bitmap_->height(); + int icon_x = (GetWidth() - icon_width) / 2; + int icon_y = ((GetHeight() - icon_height) / 2) + kSadTabOffset; + icon_bounds_.SetRect(icon_x, icon_y, icon_width, icon_height); + + int title_x = (GetWidth() - title_width_) / 2; + int title_y = icon_bounds_.bottom() + kIconTitleSpacing; + int title_height = title_font_.height(); + title_bounds_.SetRect(title_x, title_y, title_width_, title_height); + + ChromeCanvas cc(0, 0, true); + int message_width = static_cast<int>(GetWidth() * kMessageSize); + int message_height = 0; + cc.SizeStringInt(message_, message_font_, &message_width, &message_height, + ChromeCanvas::MULTI_LINE); + int message_x = (GetWidth() - message_width) / 2; + int message_y = title_bounds_.bottom() + kTitleMessageSpacing; + message_bounds_.SetRect(message_x, message_y, message_width, message_height); +} + +void SadTabView::DidChangeBounds(const CRect&, const CRect&) { + Layout(); +} + +// static +void SadTabView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + title_font_ = rb.GetFont(ResourceBundle::BaseFont). + DeriveFont(2, ChromeFont::BOLD); + message_font_ = rb.GetFont(ResourceBundle::BaseFont).DeriveFont(1); + sad_tab_bitmap_ = rb.GetBitmapNamed(IDR_SAD_TAB); + + title_ = l10n_util::GetString(IDS_SAD_TAB_TITLE); + title_width_ = title_font_.GetStringWidth(title_); + message_ = l10n_util::GetString(IDS_SAD_TAB_MESSAGE); + + initialized = true; + } +} + diff --git a/chrome/browser/views/sad_tab_view.h b/chrome/browser/views/sad_tab_view.h new file mode 100644 index 0000000..231a5b7e --- /dev/null +++ b/chrome/browser/views/sad_tab_view.h @@ -0,0 +1,81 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_SAD_TAB_H__ +#define CHROME_BROWSER_VIEWS_SAD_TAB_H__ + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/view.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// SadTabView +// +// A ChromeViews::View subclass used to render the presentation of the crashed +// "sad tab" in the browser window when a renderer is destroyed unnaturally. +// +// Note that since this view is not (currently) part of a ViewContainer or +// RootView hierarchy, it cannot respond to events or contain controls that +// do, right now it is used simply to render. Adding an extra ViewContainer to +// WebContents seemed like a lot of complexity. Ideally, perhaps WebContents' +// view portion would itself become a ViewContainer in the future, then event +// processing will work. +// +/////////////////////////////////////////////////////////////////////////////// +class SadTabView : public ChromeViews::View { + public: + SadTabView(); + virtual ~SadTabView() {} + + // Overridden from ChromeViews::View: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual void DidChangeBounds(const CRect&, const CRect&); + + private: + static void InitClass(); + + // Assorted resources for display. + static SkBitmap* sad_tab_bitmap_; + static ChromeFont title_font_; + static ChromeFont message_font_; + static std::wstring title_; + static std::wstring message_; + static int title_width_; + + // Regions within the display for different components, populated by + // Layout(). + gfx::Rect icon_bounds_; + gfx::Rect title_bounds_; + gfx::Rect message_bounds_; + + DISALLOW_EVIL_CONSTRUCTORS(SadTabView); +}; + +#endif // #ifndef CHROME_BROWSER_VIEWS_SAD_TAB_H__ diff --git a/chrome/browser/views/shelf_item_dialog.cc b/chrome/browser/views/shelf_item_dialog.cc new file mode 100644 index 0000000..bbec8e9 --- /dev/null +++ b/chrome/browser/views/shelf_item_dialog.cc @@ -0,0 +1,511 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/shelf_item_dialog.h" + +#include "base/gfx/png_decoder.h" +#include "base/string_util.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/url_fixer_upper.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/stl_util-inl.h" +#include "chrome/views/background.h" +#include "chrome/views/focus_manager.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/label.h" +#include "chrome/views/text_field.h" +#include "generated_resources.h" +#include "net/base/net_util.h" + +using ChromeViews::ColumnSet; +using ChromeViews::GridLayout; + +// Preferred height of the table. +static const int kTableWidth = 300; + +// The default favicon. +static SkBitmap* default_fav_icon = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// +// A table model to represent the list of URLS that we the user might want to +// bookmark. +// +//////////////////////////////////////////////////////////////////////////////// + +// How long we query entry points for. +static const int kPossibleURLTimeScope = 30; + +class PossibleURLModel : public ChromeViews::TableModel { + public: + PossibleURLModel() : profile_(NULL) { + if (!default_fav_icon) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + default_fav_icon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); + } + } + + virtual ~PossibleURLModel() { + } + + void Reload(Profile *profile) { + profile_ = profile; + consumer_.CancelAllRequests(); + HistoryService* hs = + profile->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + history::QueryOptions options; + options.end_time = Time::Now(); + options.begin_time = + options.end_time - TimeDelta::FromDays(kPossibleURLTimeScope); + options.most_recent_visit_only = true; + options.max_count = 50; + + hs->QueryHistory(std::wstring(), options, &consumer_, + NewCallback(this, &PossibleURLModel::OnHistoryQueryComplete)); + } + } + + void OnHistoryQueryComplete(HistoryService::Handle h, + history::QueryResults* result) { + results_.Swap(result); + + // The old version of this code would filter out all but the most recent + // visit to each host, plus all typed URLs and AUTO_BOOKMARK transitions. I + // think this dialog has a lot of work, and I'm not sure those old + // conditions are correct (the results look about equal quality for my + // history with and without those conditions), so I'm not spending time + // re-implementing them here. They used to be implemented in the history + // service, but I think they should be implemented here because that was + // pretty specific behavior that shouldn't be generally exposed. + + fav_icon_map_.clear(); + if (observer_) + observer_->OnModelChanged(); + } + + virtual int RowCount() { + return static_cast<int>(results_.size()); + } + + const GURL& GetURL(int row) { + if (row < 0 || row >= RowCount()) { + NOTREACHED(); + return GURL::EmptyGURL(); + } + return results_[row].url(); + } + + const std::wstring& GetTitle(int row) { + if (row < 0 || row >= RowCount()) { + NOTREACHED(); + return EmptyWString(); + } + return results_[row].title(); + } + + virtual std::wstring GetText(int row, int col_id) { + if (row < 0 || row >= RowCount()) { + NOTREACHED(); + return std::wstring(); + } + + if (col_id == IDS_ASI_PAGE_COLUMN) + return GetTitle(row); + + // TODO(brettw): this should probably pass the GURL up so the URL elider + // can be used at a higher level when we know the width. + return gfx::ElideUrl(GetURL(row), ChromeFont(), 0, profile_ ? + profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : + std::wstring()); + } + + virtual SkBitmap GetIcon(int row) { + if (row < 0 || row >= RowCount()) { + NOTREACHED(); + return *default_fav_icon; + } + + const history::URLResult& result = results_[row]; + FavIconMap::iterator i = fav_icon_map_.find(result.id()); + if (i != fav_icon_map_.end()) { + if (!i->second.isNull()) + return i->second; + } else if (profile_) { + HistoryService* hs = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) { + CancelableRequestProvider::Handle h = + hs->GetFavIconForURL( + result.url(), &consumer_, + NewCallback(this, &PossibleURLModel::OnFavIconAvailable)); + consumer_.SetClientData(hs, h, result.id()); + } + } + return *default_fav_icon; + } + + virtual void OnFavIconAvailable( + HistoryService::Handle h, + bool fav_icon_available, + scoped_refptr<RefCountedBytes> data, + bool expired, + GURL icon_url) { + if (profile_) { + HistoryService* hs = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + history::URLID pid = consumer_.GetClientData(hs, h); + if (pid) { + SkBitmap bm; + if (fav_icon_available) { + // The decoder will leave our bitmap empty on error. + PNGDecoder::Decode(&data->data, &bm); + } + + // Store the bitmap. We store it even if it is empty to make sure we + // don't query it again. + fav_icon_map_[pid] = bm; + if (!bm.isNull() && observer_) { + for (size_t i = 0; i < results_.size(); ++i) { + if (results_[i].id() == pid) + observer_->OnItemsChanged(static_cast<int>(i), 1); + } + } + } + } + } + + virtual void SetObserver(ChromeViews::TableModelObserver* observer) { + observer_ = observer; + } + + private: + // The current profile. + Profile* profile_; + + // Our observer. + ChromeViews::TableModelObserver* observer_; + + // Our consumer for favicon requests. + CancelableRequestConsumerT<history::URLID, NULL> consumer_; + + // The results provided by the history service. + history::QueryResults results_; + + // Map URLID -> Favicon. If we queried for a favicon and there is none, that + // URL will have an entry, and the bitmap will be empty. + typedef std::map<history::URLID, SkBitmap> FavIconMap; + FavIconMap fav_icon_map_; + + DISALLOW_EVIL_CONSTRUCTORS(PossibleURLModel); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// ShelfItemDialog implementation +// +//////////////////////////////////////////////////////////////////////////////// +ShelfItemDialog::ShelfItemDialog(ShelfItemDialogDelegate* delegate, + Profile* profile, + bool show_title) + : dialog_(NULL), + profile_(profile), + expected_title_handle_(0), + delegate_(delegate) { + DCHECK(profile_); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + url_table_model_.reset(new PossibleURLModel()); + + ChromeViews::TableColumn col1(IDS_ASI_PAGE_COLUMN, + ChromeViews::TableColumn::LEFT, -1, + 50); + ChromeViews::TableColumn col2(IDS_ASI_URL_COLUMN, + ChromeViews::TableColumn::LEFT, -1, + 50); + std::vector<ChromeViews::TableColumn> cols; + cols.push_back(col1); + cols.push_back(col2); + + url_table_ = new ChromeViews::TableView(url_table_model_.get(), + cols, + ChromeViews::ICON_AND_TEXT, + true, + true, + true); + url_table_->SetObserver(this); + + // Yummy layout code. + const int labels_column_set_id = 0; + const int single_column_view_set_id = 1; + GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + ColumnSet* column_set = layout->AddColumnSet(labels_column_set_id); + column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0, + GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + + column_set = layout->AddColumnSet(single_column_view_set_id); + column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1, + GridLayout::FIXED, kTableWidth, 0); + + if (show_title) { + layout->StartRow(0, labels_column_set_id); + ChromeViews::Label* title_label = new ChromeViews::Label(); + title_label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + title_label->SetText(l10n_util::GetString(IDS_ASI_TITLE_LABEL)); + layout->AddView(title_label); + + title_field_ = new ChromeViews::TextField(); + title_field_->SetController(this); + layout->AddView(title_field_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + } else { + title_field_ = NULL; + } + + layout->StartRow(0, labels_column_set_id); + ChromeViews::Label* url_label = new ChromeViews::Label(); + url_label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + url_label->SetText(l10n_util::GetString(IDS_ASI_URL)); + layout->AddView(url_label); + + url_field_ = new ChromeViews::TextField(); + url_field_->SetController(this); + layout->AddView(url_field_); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + layout->StartRow(0, single_column_view_set_id); + ChromeViews::Label* description_label = new ChromeViews::Label(); + description_label->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); + description_label->SetText(l10n_util::GetString(IDS_ASI_DESCRIPTION)); + description_label->SetFont( + description_label->GetFont().DeriveFont(0, ChromeFont::BOLD)); + layout->AddView(description_label); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(1, single_column_view_set_id); + layout->AddView(url_table_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + AddAccelerator(ChromeViews::Accelerator(VK_ESCAPE, false, false, false)); + AddAccelerator(ChromeViews::Accelerator(VK_RETURN, false, false, false)); +} + +ShelfItemDialog::~ShelfItemDialog() { + url_table_->SetModel(NULL); +} + +void ShelfItemDialog::Show(HWND parent) { + DCHECK(!dialog_); + dialog_ = + ChromeViews::Window::CreateChromeWindow(parent, gfx::Rect(), this, this); + dialog_->Show(); + if (title_field_) { + title_field_->SetText(l10n_util::GetString(IDS_ASI_DEFAULT_TITLE)); + title_field_->SelectAll(); + title_field_->RequestFocus(); + } else { + url_field_->SelectAll(); + url_field_->RequestFocus(); + } + url_table_model_->Reload(profile_); +} + +void ShelfItemDialog::Close() { + DCHECK(dialog_); + dialog_->Close(); +} + +std::wstring ShelfItemDialog::GetWindowTitle() const { + return l10n_util::GetString(IDS_ASI_ADD_TITLE); +} + +bool ShelfItemDialog::IsModal() const { + return true; +} + +void ShelfItemDialog::WindowClosing() { +} + +std::wstring ShelfItemDialog::GetDialogButtonLabel(DialogButton button) const { + if (button == DialogDelegate::DIALOGBUTTON_OK) + return l10n_util::GetString(IDS_ASI_ADD); + return std::wstring(); +} + +void ShelfItemDialog::OnURLInfoAvailable( + HistoryService::Handle handle, + bool success, + const history::URLRow* info, + history::VisitVector* unused) { + if (handle != expected_title_handle_) + return; + + std::wstring s; + if (success) + s = info->title(); + if (s.empty()) + s = l10n_util::GetString(IDS_ASI_DEFAULT_TITLE); + + if (title_field_) { + // expected_title_handle_ is reset if the title textfield is edited so we + // can safely set the value. + title_field_->SetText(s); + title_field_->SelectAll(); + } + expected_title_handle_ = 0; +} + +void ShelfItemDialog::InitiateTitleAutoFill(const GURL& url) { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (!hs) + return; + + if (expected_title_handle_) + hs->CancelRequest(expected_title_handle_); + + expected_title_handle_ = hs->QueryURL(url, false, &history_consumer_, + NewCallback(this, &ShelfItemDialog::OnURLInfoAvailable)); +} + +void ShelfItemDialog::ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents) { + // If the user has edited the title field we no longer want to autofill it + // so we reset the expected handle to an impossible value. + if (sender == title_field_) + expected_title_handle_ = 0; + dialog_->UpdateDialogButtons(); +} + +bool ShelfItemDialog::Accept() { + if (!IsDialogButtonEnabled(DIALOGBUTTON_OK)) { + if (!GetInputURL().is_valid()) + url_field_->RequestFocus(); + else if (title_field_) + title_field_->RequestFocus(); + return false; + } + PerformModelChange(); + return true; +} + +bool ShelfItemDialog::IsDialogButtonEnabled(DialogButton button) const { + if (button == DIALOGBUTTON_OK) + return GetInputURL().is_valid(); + return true; +} + +void ShelfItemDialog::PerformModelChange() { + DCHECK(delegate_); + GURL url(GetInputURL()); + const std::wstring title = + title_field_ ? title_field_->GetText() : std::wstring(); + delegate_->AddBookmark(this, title, url); +} + +void ShelfItemDialog::GetPreferredSize(CSize *out) { + DCHECK(out); + *out = ChromeViews::Window::GetLocalizedContentsSize( + IDS_SHELFITEM_DIALOG_WIDTH_CHARS, + IDS_SHELFITEM_DIALOG_HEIGHT_LINES).ToSIZE(); +} + +bool ShelfItemDialog::AcceleratorPressed( + const ChromeViews::Accelerator& accelerator) { + if (accelerator.GetKeyCode() == VK_ESCAPE) { + dialog_->Close(); + } else if (accelerator.GetKeyCode() == VK_RETURN) { + ChromeViews::FocusManager* fm = ChromeViews::FocusManager::GetFocusManager( + GetViewContainer()->GetHWND()); + if (fm->GetFocusedView() == url_table_) { + // Return on table behaves like a double click. + OnDoubleClick(); + } else if (fm->GetFocusedView()== url_field_) { + // Return on the url field accepts the input if url is valid. If the URL + // is invalid, focus is left on the url field. + if (GetInputURL().is_valid()) { + PerformModelChange(); + if (dialog_) + dialog_->Close(); + } else { + url_field_->SelectAll(); + } + } else if (title_field_ && fm->GetFocusedView() == title_field_) { + url_field_->SelectAll(); + url_field_->RequestFocus(); + } + } + return true; +} + +void ShelfItemDialog::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void ShelfItemDialog::OnSelectionChanged() { + int selection = url_table_->FirstSelectedRow(); + if (selection >= 0 && selection < url_table_model_->RowCount()) { + url_field_->SetText( + UTF8ToWide(url_table_model_->GetURL(selection).spec())); + if (title_field_) + title_field_->SetText(url_table_model_->GetTitle(selection)); + dialog_->UpdateDialogButtons(); + } +} + +void ShelfItemDialog::OnDoubleClick() { + int selection = url_table_->FirstSelectedRow(); + if (selection >= 0 && selection < url_table_model_->RowCount()) { + OnSelectionChanged(); + PerformModelChange(); + if (dialog_) + dialog_->Close(); + } +} + +GURL ShelfItemDialog::GetInputURL() const { + return GURL(URLFixerUpper::FixupURL(url_field_->GetText(), L"")); +} diff --git a/chrome/browser/views/shelf_item_dialog.h b/chrome/browser/views/shelf_item_dialog.h new file mode 100644 index 0000000..e5c0b7d --- /dev/null +++ b/chrome/browser/views/shelf_item_dialog.h @@ -0,0 +1,155 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_SHELF_ITEM_DIALOG_H__ +#define CHROME_BROWSER_VIEWS_SHELF_ITEM_DIALOG_H__ + +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/history/history.h" +#include "chrome/views/dialog_delegate.h" +#include "chrome/views/native_button.h" +#include "chrome/views/table_view.h" +#include "chrome/views/text_field.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" + +namespace ChromeViews { +class Button; +class Label; +} + +class PossibleURLModel; +class Profile; +class ShelfItemDialog; + +// TODO(sky): rename this, perhaps to URLPicker. + +// ShelfItemDialog delegate. Notified when the user accepts the entry. +class ShelfItemDialogDelegate { + public: + virtual void AddBookmark(ShelfItemDialog* dialog, + const std::wstring& title, + const GURL& url) = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// This class implements the dialog that let the user add a bookmark or page +// to the list of urls to open on startup. +// ShelfItemDialog deletes itself when the dialog is closed. +// +//////////////////////////////////////////////////////////////////////////////// +class ShelfItemDialog : public ChromeViews::View, + public ChromeViews::DialogDelegate, + public ChromeViews::TextField::Controller, + public ChromeViews::TableViewObserver { + public: + ShelfItemDialog(ShelfItemDialogDelegate* delegate, + Profile* profile, + bool show_title); + virtual ~ShelfItemDialog(); + + // Show the dialog on the provided contents. + virtual void Show(HWND parent); + + // Closes the dialog. + void Close(); + + // DialogDelegate. + virtual std::wstring GetWindowTitle() const; + virtual bool IsModal() const; + virtual void WindowClosing(); + virtual std::wstring GetDialogButtonLabel(DialogButton button) const; + virtual bool Accept(); + virtual bool IsDialogButtonEnabled(DialogButton button) const; + + // TextField::Controller. + virtual void ContentsChanged(ChromeViews::TextField* sender, + const std::wstring& new_contents); + virtual void HandleKeystroke(ChromeViews::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags) {} + + // Overridden from View. + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void GetPreferredSize(CSize *out); + virtual bool AcceleratorPressed(const ChromeViews::Accelerator& accelerator); + + // TableViewObserver. + virtual void OnSelectionChanged(); + virtual void OnDoubleClick(); + + private: + // Modify the model from the user interface. + void PerformModelChange(); + + // Fetch the title for the entered URL. If we get the title in time before + // the user starts to modify the title field, the title field is changed. + void InitiateTitleAutoFill(const GURL& url); + + // Invoked by the history system when a title becomes available. + void OnURLInfoAvailable(HistoryService::Handle handle, + bool success, + const history::URLRow* info, + history::VisitVector* unused); + + // Returns the URL the user has typed. + GURL GetInputURL() const; + + // The dialog controller for our current dialog. + ChromeViews::Window* dialog_; + + // Profile. + Profile* profile_; + + // URL Field. + ChromeViews::TextField* url_field_; + + // Title field. This is NULL if we're not showing the title. + ChromeViews::TextField* title_field_; + + // The table model. + scoped_ptr<PossibleURLModel> url_table_model_; + + // The table of visited urls. + ChromeViews::TableView* url_table_; + + // Handle of the title request we are expecting. + CancelableRequestProvider::Handle expected_title_handle_; + + // The consumer object for the history database. + CancelableRequestConsumer history_consumer_; + + // The delegate. + ShelfItemDialogDelegate* delegate_; + + DISALLOW_EVIL_CONSTRUCTORS(ShelfItemDialog); +}; + +#endif // CHROME_BROWSER_VIEWS_SHELF_ITEM_DIALOG_H__ diff --git a/chrome/browser/views/shell_dialogs.cc b/chrome/browser/views/shell_dialogs.cc new file mode 100644 index 0000000..bd4b19b --- /dev/null +++ b/chrome/browser/views/shell_dialogs.cc @@ -0,0 +1,551 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/shell_dialogs.h" + +#include <windows.h> +#include <Commdlg.h> +#include <shlobj.h> + +#include <algorithm> +#include <map> + +#include "base/file_util.h" +#include "base/registry.h" +#include "base/thread.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/win_util.h" +#include "generated_resources.h" + +class ShellDialogThread : public Thread { + public: + ShellDialogThread() : Thread("Chrome_ShellDialogThread") { } + + protected: + void Init() { + // Initializes the COM library on the current thread. + CoInitialize(NULL); + } + + void CleanUp() { + // Closes the COM library on the current thread. CoInitialize must + // be balanced by a corresponding call to CoUninitialize. + CoUninitialize(); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(ShellDialogThread); +}; + +/////////////////////////////////////////////////////////////////////////////// +// A base class for all shell dialog implementations that handles showing a +// shell dialog modally on its own thread. +class BaseShellDialogImpl { + public: + BaseShellDialogImpl(); + virtual ~BaseShellDialogImpl(); + + protected: + // Represents a run of a dialog. + struct RunState { + // Owning HWND, may be null. + HWND owner; + + // Thread dialog is run on. + Thread* dialog_thread; + }; + + // Called at the beginning of a modal dialog run. Disables the owner window + // and tracks it. Returns the message loop of the thread that the dialog will + // be run on. + RunState BeginRun(HWND owner); + + // Cleans up after a dialog run. If the run_state has a valid HWND this makes + // sure that the window is enabled. This is essential because BeginRun + // aggressively guards against multiple modal dialogs per HWND. Must be called + // on the UI thread after the result of the dialog has been determined. + // + // In addition this deletes the Thread in RunState. + void EndRun(RunState run_state); + + // Returns true if a modal shell dialog is currently active for the specified + // owner. Must be called on the UI thread. + bool IsRunningDialogForOwner(HWND owner) const; + + // Disables the window |owner|. Can be run from either the ui or the dialog + // thread. Can be called on either the UI or the dialog thread. This function + // is called on the dialog thread after the modal Windows Common dialog + // functions return because Windows automatically re-enables the owning + // window when those functions return, but we don't actually want them to be + // re-enabled until the response of the dialog propagates back to the UI + // thread, so we disable the owner manually after the Common dialog function + // returns. + void DisableOwner(HWND owner); + + // The UI thread's message loop. + MessageLoop* ui_loop_; + + private: + // Creates a thread to run a shell dialog on. Each dialog requires its own + // thread otherwise in some situations where a singleton owns a single + // instance of this object we can have a situation where a modal dialog in + // one window blocks the appearance of a modal dialog in another. + static Thread* CreateDialogThread(); + + // Enables the window |owner_|. Can only be run from the ui thread. + void EnableOwner(HWND owner); + + // A list of windows that currently own active shell dialogs for this + // instance. For example, if the DownloadManager owns an instance of this + // object and there are two browser windows open both with Save As dialog + // boxes active, this list will consist of the two browser windows' HWNDs. + // The derived class must call EndRun once the dialog is done showing to + // remove the owning HWND from this list. + // This object is static since it is maintained for all instances of this + // object - i.e. you can't have a font picker and a file picker open for the + // same owner, even though they might be represented by different instances + // of this object. + // This set only contains non-null HWNDs. NULL hwnds are not added to this + // list. + typedef std::set<HWND> Owners; + static Owners owners_; + static int instance_count_; + + DISALLOW_EVIL_CONSTRUCTORS(BaseShellDialogImpl); +}; + +// static +BaseShellDialogImpl::Owners BaseShellDialogImpl::owners_; +int BaseShellDialogImpl::instance_count_ = 0; + +BaseShellDialogImpl::BaseShellDialogImpl() + : ui_loop_(MessageLoop::current()) { + ++instance_count_; +} + +BaseShellDialogImpl::~BaseShellDialogImpl() { + // All runs should be complete by the time this is called! + if (--instance_count_ == 0) + DCHECK(owners_.empty()); +} + +BaseShellDialogImpl::RunState BaseShellDialogImpl::BeginRun(HWND owner) { + // Cannot run a modal shell dialog if one is already running for this owner. + DCHECK(!IsRunningDialogForOwner(owner)); + // The owner must be a top level window, otherwise we could end up with two + // entries in our map for the same top level window. + DCHECK(!owner || owner == GetAncestor(owner, GA_ROOT)); + RunState run_state; + run_state.dialog_thread = CreateDialogThread(); + run_state.owner = owner; + if (owner) { + owners_.insert(owner); + DisableOwner(owner); + } + return run_state; +} + +void BaseShellDialogImpl::EndRun(RunState run_state) { + if (run_state.owner) { + DCHECK(IsRunningDialogForOwner(run_state.owner)); + EnableOwner(run_state.owner); + DCHECK(owners_.find(run_state.owner) != owners_.end()); + owners_.erase(run_state.owner); + } + DCHECK(run_state.dialog_thread); + delete run_state.dialog_thread; +} + +bool BaseShellDialogImpl::IsRunningDialogForOwner(HWND owner) const { + return (owner && owners_.find(owner) != owners_.end()); +} + +void BaseShellDialogImpl::DisableOwner(HWND owner) { + if (IsWindow(owner)) + EnableWindow(owner, FALSE); +} + +// static +Thread* BaseShellDialogImpl::CreateDialogThread() { + Thread* thread = new ShellDialogThread; + bool started = thread->Start(); + DCHECK(started); + return thread; +} + +void BaseShellDialogImpl::EnableOwner(HWND owner) { + if (IsWindow(owner)) + EnableWindow(owner, TRUE); +} + +// Implementation of SelectFileDialog that shows a Windows common dialog for +// choosing a file or folder. +class SelectFileDialogImpl : public SelectFileDialog, + public BaseShellDialogImpl { + public: + explicit SelectFileDialogImpl(Listener* listener); + virtual ~SelectFileDialogImpl(); + + // SelectFileDialog implementation: + virtual void SelectFile(Type type, const std::wstring& title, + const std::wstring& default_path, HWND owning_hwnd, + void* params); + virtual bool IsRunning(HWND owning_hwnd) const; + virtual void ListenerDestroyed(); + + private: + // Shows the file selection dialog modal to |owner| and calls the result + // back on the ui thread. Run on the dialog thread. + void ExecuteSelectFile(Type type, + const std::wstring& title, + const std::wstring& default_path, + RunState run_state, + void* params); + + // Notifies the listener that a folder was chosen. Run on the ui thread. + void FileSelected(const std::wstring& path, void* params, RunState run_state); + + // Notifies the listener that no file was chosen (the action was canceled). + // Run on the ui thread. + void FileNotSelected(void* params, RunState run_state); + + // Runs a Folder selection dialog box, passes back the selected folder in + // |path| and returns true if the user clicks OK. If the user cancels the + // dialog box the value in |path| is not modified and returns false. |title| + // is the user-supplied title text to show for the dialog box. Run on the + // dialog thread. + bool RunSelectFolderDialog(const std::wstring& title, + HWND owner, + std::wstring* path); + + // Runs an Open file dialog box, with similar semantics for input paramaters + // as RunSelectFolderDialog. + bool RunOpenFileDialog(const std::wstring& title, + HWND owner, + std::wstring* path); + + // The listener to be notified of selection completion. + Listener* listener_; + + DISALLOW_EVIL_CONSTRUCTORS(SelectFileDialogImpl); +}; + +SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener) + : listener_(listener), + BaseShellDialogImpl() { +} + +SelectFileDialogImpl::~SelectFileDialogImpl() { +} + +void SelectFileDialogImpl::SelectFile(Type type, + const std::wstring& title, + const std::wstring& default_path, + HWND owner, + void* params) { + RunState run_state = BeginRun(owner); + run_state.dialog_thread->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &SelectFileDialogImpl::ExecuteSelectFile, type, + title, default_path, run_state, params)); +} + +bool SelectFileDialogImpl::IsRunning(HWND owning_hwnd) const { + return listener_ && IsRunningDialogForOwner(owning_hwnd); +} + +void SelectFileDialogImpl::ListenerDestroyed() { + // Our associated listener has gone away, so we shouldn't call back to it if + // our worker thread returns after the listener is dead. + listener_ = NULL; +} + +void SelectFileDialogImpl::ExecuteSelectFile(Type type, + const std::wstring& title, + const std::wstring& default_path, + RunState run_state, + void* params) { + std::wstring path = default_path; + bool success = false; + if (type == SELECT_FOLDER) { + success = RunSelectFolderDialog(title, run_state.owner, &path); + } else if (type == SELECT_SAVEAS_FILE) { + success = win_util::SaveFileAs(run_state.owner, default_path, &path); + DisableOwner(run_state.owner); + } else if (type == SELECT_OPEN_FILE) { + success = RunOpenFileDialog(title, run_state.owner, &path); + } + + if (success) { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFileDialogImpl::FileSelected, path, params, run_state)); + } else { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFileDialogImpl::FileNotSelected, params, run_state)); + } +} + +void SelectFileDialogImpl::FileSelected(const std::wstring& selected_folder, + void* params, + RunState run_state) { + if (listener_) + listener_->FileSelected(selected_folder, params); + EndRun(run_state); +} + +void SelectFileDialogImpl::FileNotSelected(void* params, RunState run_state) { + if (listener_) + listener_->FileSelectionCanceled(params); + EndRun(run_state); +} + +bool SelectFileDialogImpl::RunSelectFolderDialog(const std::wstring& title, + HWND owner, + std::wstring* path) { + DCHECK(path); + + wchar_t dir_buffer[MAX_PATH + 1]; + + BROWSEINFO browse_info = {0}; + browse_info.hwndOwner = owner; + browse_info.lpszTitle = title.c_str(); + browse_info.pszDisplayName = dir_buffer; + browse_info.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + LPITEMIDLIST list = SHBrowseForFolder(&browse_info); + DisableOwner(owner); + if (list) { + wchar_t out_dir_buffer[MAX_PATH + 1]; + if (SHGetPathFromIDList(list, out_dir_buffer)) { + *path = out_dir_buffer; + + // According to MSDN, win2000 will not resolve shortcuts, so we do it + // ourself. + file_util::ResolveShortcut(path); + return true; + } + CoTaskMemFree(list); + } + return false; +} + +bool SelectFileDialogImpl::RunOpenFileDialog(const std::wstring& title, + HWND owner, + std::wstring* path) { + OPENFILENAME ofn; + // We must do this otherwise the ofn's FlagsEx may be initialized to random + // junk in release builds which can cause the Places Bar not to show up! + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = owner; + + wchar_t filename[MAX_PATH]; + memcpy(filename, path->c_str(), (path->length()+1) * sizeof(wchar_t)); + + ofn.lpstrFile = filename; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_FILEMUSTEXIST; + + // TODO(beng): (http://b/issue?id=1126563) edit the filter options in the + // dropdown list. + bool success = !!GetOpenFileName(&ofn); + DisableOwner(owner); + if (success) + *path = filename; + return success; +} + +// static +SelectFileDialog* SelectFileDialog::Create(Listener* listener) { + return new SelectFileDialogImpl(listener); +} + +/////////////////////////////////////////////////////////////////////////////// +// SelectFontDialogImpl +// Implementation of SelectFontDialog that shows a Windows common dialog for +// choosing a font. +class SelectFontDialogImpl : public SelectFontDialog, + public BaseShellDialogImpl { + public: + explicit SelectFontDialogImpl(Listener* listener); + virtual ~SelectFontDialogImpl(); + + // SelectFontDialog implementation: + virtual void SelectFont(HWND owning_hwnd, void* params); + virtual void SelectFont(HWND owning_hwnd, + void* params, + const std::wstring& font_name, + int font_size); + virtual bool IsRunning(HWND owning_hwnd) const; + virtual void ListenerDestroyed(); + + private: + // Shows the font selection dialog modal to |owner| and calls the result + // back on the ui thread. Run on the dialog thread. + void ExecuteSelectFont(RunState run_state, void* params); + + // Shows the font selection dialog modal to |owner| and calls the result + // back on the ui thread. Run on the dialog thread. + void ExecuteSelectFontWithNameSize(RunState run_state, + void* params, + const std::wstring& font_name, + int font_size); + + // Notifies the listener that a font was chosen. Run on the ui thread. + void FontSelected(LOGFONT logfont, void* params, RunState run_state); + + // Notifies the listener that no font was chosen (the action was canceled). + // Run on the ui thread. + void FontNotSelected(void* params, RunState run_state); + + // The listener to be notified of selection completion. + Listener* listener_; + + DISALLOW_EVIL_CONSTRUCTORS(SelectFontDialogImpl); +}; + +SelectFontDialogImpl::SelectFontDialogImpl(Listener* listener) + : listener_(listener), + BaseShellDialogImpl() { +} + +SelectFontDialogImpl::~SelectFontDialogImpl() { +} + +void SelectFontDialogImpl::SelectFont(HWND owner, void* params) { + RunState run_state = BeginRun(owner); + run_state.dialog_thread->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &SelectFontDialogImpl::ExecuteSelectFont, + run_state, params)); +} + +void SelectFontDialogImpl::SelectFont(HWND owner, void* params, + const std::wstring& font_name, + int font_size) { + RunState run_state = BeginRun(owner); + run_state.dialog_thread->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, + &SelectFontDialogImpl::ExecuteSelectFontWithNameSize, run_state, + params, font_name, font_size)); +} + +bool SelectFontDialogImpl::IsRunning(HWND owning_hwnd) const { + return listener_ && IsRunningDialogForOwner(owning_hwnd); +} + +void SelectFontDialogImpl::ListenerDestroyed() { + // Our associated listener has gone away, so we shouldn't call back to it if + // our worker thread returns after the listener is dead. + listener_ = NULL; +} + +void SelectFontDialogImpl::ExecuteSelectFont(RunState run_state, void* params) { + LOGFONT logfont; + CHOOSEFONT cf; + cf.lStructSize = sizeof(cf); + cf.hwndOwner = run_state.owner; + cf.lpLogFont = &logfont; + cf.Flags = CF_SCREENFONTS; + bool success = !!ChooseFont(&cf); + DisableOwner(run_state.owner); + if (success) { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFontDialogImpl::FontSelected, logfont, params, run_state)); + } else { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFontDialogImpl::FontNotSelected, params, run_state)); + } +} + +void SelectFontDialogImpl::ExecuteSelectFontWithNameSize( + RunState run_state, void* params, const std::wstring& font_name, + int font_size) { + // Create the HFONT from font name and size. + HDC hdc = GetDC(NULL); + long lf_height = -MulDiv(font_size, GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(NULL, hdc); + HFONT hf = ::CreateFont(lf_height, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + font_name.c_str()); + LOGFONT logfont; + GetObject(hf, sizeof(LOGFONT), &logfont); + CHOOSEFONT cf; + cf.lStructSize = sizeof(cf); + cf.hwndOwner = run_state.owner; + cf.lpLogFont = &logfont; + // Limit the list to a reasonable subset of fonts. + // TODO : get rid of style selector and script selector + // 1. List only truetype font + // 2. Exclude vertical fonts (whose names begin with '@') + // 3. Exclude symbol and OEM fonts + // 4. Limit the size to [8, 40]. + // See http://msdn.microsoft.com/en-us/library/ms646832(VS.85).aspx + cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_TTONLY | + CF_NOVERTFONTS | CF_SCRIPTSONLY | CF_LIMITSIZE; + + // These limits are arbitrary and needs to be revisited. Is it bad + // to clamp the size at 40 from A11Y point of view? + cf.nSizeMin = 8; + cf.nSizeMax = 40; + + bool success = !!ChooseFont(&cf); + DisableOwner(run_state.owner); + if (success) { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFontDialogImpl::FontSelected, logfont, params, run_state)); + } else { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SelectFontDialogImpl::FontNotSelected, params, run_state)); + } +} + +void SelectFontDialogImpl::FontSelected(LOGFONT logfont, + void* params, + RunState run_state) { + if (listener_) { + HFONT font = CreateFontIndirect(&logfont); + if (font) { + listener_->FontSelected(ChromeFont::CreateFont(font), params); + DeleteObject(font); + } else { + listener_->FontSelectionCanceled(params); + } + } + EndRun(run_state); +} + +void SelectFontDialogImpl::FontNotSelected(void* params, RunState run_state) { + if (listener_) + listener_->FontSelectionCanceled(params); + EndRun(run_state); +} + +// static +SelectFontDialog* SelectFontDialog::Create(Listener* listener) { + return new SelectFontDialogImpl(listener); +} diff --git a/chrome/browser/views/star_toggle.cc b/chrome/browser/views/star_toggle.cc new file mode 100644 index 0000000..e480c40 --- /dev/null +++ b/chrome/browser/views/star_toggle.cc @@ -0,0 +1,105 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/star_toggle.h" + +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/resource_bundle.h" + +StarToggle::StarToggle(Delegate* delegate) + : delegate_(delegate), + state_(false), + change_state_immediately_(false) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + state_off_ = rb.GetBitmapNamed(IDR_CONTENT_STAR_OFF); + state_on_ = rb.GetBitmapNamed(IDR_CONTENT_STAR_ON); + SetFocusable(true); +} + +StarToggle::~StarToggle() { +} + +void StarToggle::SetState(bool s) { + if (s != state_) { + state_ = s; + SchedulePaint(); + } +} + +bool StarToggle::GetState() const { + return state_; +} + +void StarToggle::Paint(ChromeCanvas* canvas) { + PaintFocusBorder(canvas); + canvas->DrawBitmapInt(state_ ? *state_on_ : *state_off_, + (GetWidth() - state_off_->width()) / 2, + (GetHeight() - state_off_->height()) / 2); +} + +void StarToggle::GetPreferredSize(CSize* out) { + out->cx = state_off_->width(); + out->cy = state_off_->height(); +} + +bool StarToggle::OnMouseDragged(const ChromeViews::MouseEvent& e) { + return e.IsLeftMouseButton(); +} + +bool StarToggle::OnMousePressed(const ChromeViews::MouseEvent& e) { + if (e.IsLeftMouseButton() && HitTest(e.GetLocation())) { + RequestFocus(); + return true; + } + return false; +} + +void StarToggle::OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled) { + if (e.IsLeftMouseButton() && HitTest(e.GetLocation())) + SwitchState(); +} + +bool StarToggle::OnKeyPressed(const ChromeViews::KeyEvent& e) { + if ((e.GetCharacter() == L' ') || (e.GetCharacter() == L'\n')) { + SwitchState(); + return true; + } + return false; +} + +void StarToggle::SwitchState() { + const bool new_state = !state_; + if (change_state_immediately_) + state_ = new_state; + SchedulePaint(); + delegate_->StarStateChanged(new_state); +}
\ No newline at end of file diff --git a/chrome/browser/views/star_toggle.h b/chrome/browser/views/star_toggle.h new file mode 100644 index 0000000..0a329fe --- /dev/null +++ b/chrome/browser/views/star_toggle.h @@ -0,0 +1,92 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_STAR_TOGGLE_H__ +#define CHROME_BROWSER_VIEWS_STAR_TOGGLE_H__ + +#include "chrome/views/view.h" +#include "chrome/views/event.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// A view subclass to implement the star button. The star button notifies its +// Delegate when the state changes. +// +//////////////////////////////////////////////////////////////////////////////// +class StarToggle : public ChromeViews::View { + public: + class Delegate { + public: + // Called when the star is toggled. + virtual void StarStateChanged(bool state) = 0; + }; + + explicit StarToggle(Delegate* delegate); + virtual ~StarToggle(); + + // Set whether the star is checked. + void SetState(bool s); + bool GetState() const; + + // If true (the default) the state is immediately changed on a mouse release. + // If false, on mouse release the delegate is notified, but the state is not + // changed. + void set_change_state_immediately(bool value) { + change_state_immediately_ = value; + } + + // Check/uncheck the star. + void SwitchState(); + + // Overriden from view. + void Paint(ChromeCanvas* canvas); + void GetPreferredSize(CSize* out); + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + virtual bool OnMouseDragged(const ChromeViews::MouseEvent& event); + virtual void OnMouseReleased(const ChromeViews::MouseEvent& e, bool canceled); + bool OnKeyPressed(const ChromeViews::KeyEvent& e); + + private: + // The state. + bool state_; + + // Our bitmap. + SkBitmap* state_off_; + SkBitmap* state_on_; + + // Parent to be notified. + Delegate* delegate_; + + // See note in setter. + bool change_state_immediately_; + + DISALLOW_EVIL_CONSTRUCTORS(StarToggle); +}; + +#endif // CHROME_BROWSER_VIEWS_STAR_TOGGLE_H__ diff --git a/chrome/browser/views/status_bubble.cc b/chrome/browser/views/status_bubble.cc new file mode 100644 index 0000000..96398fa --- /dev/null +++ b/chrome/browser/views/status_bubble.cc @@ -0,0 +1,671 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/status_bubble.h" + +#include <algorithm> + +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/animation.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/hwnd_view_container.h" +#include "chrome/views/label.h" +#include "chrome/views/view_container.h" +#include "net/base/net_util.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkRect.h" + +#include "generated_resources.h" + +// The color of the background bubble. +static const SkColor kBubbleColor = SkColorSetRGB(222, 234, 248); + +// The alpha and color of the bubble's shadow. +static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0); + +// How wide the bubble's shadow is. +static const int kShadowSize = 1; + +// The roundedness of the edges of our bubble. +static const int kBubbleCornerRadius = 4; + +// How close the mouse can get to the infobubble before it starts sliding +// off-screen. +static const int kMousePadding = 20; + +// The color of the text +static const SkColor kTextColor = SkColorSetRGB(100, 100, 100); + +// The color of the highlight text +static const SkColor kTextHighlightColor = SkColorSetRGB(242, 250, 255); + +static const int kTextPadding = 3; +static const int kTextPositionX = 4; +static const int kTextPositionY = 1; + +// Delays before we start hiding or showing the bubble after we receive a +// show or hide request. +static const int kShowDelay = 80; +static const int kHideDelay = 250; + +// How long each fade should last for. +static const int kShowFadeDurationMS = 120; +static const int kHideFadeDurationMS = 200; +static const int kFramerate = 25; + +// View ----------------------------------------------------------------------- +// StatusView manages the display of the bubble, applying text changes and +// fading in or out the bubble as required. +class StatusBubble::StatusView : public ChromeViews::Label, + public Animation, + public AnimationDelegate { + public: + StatusView(StatusBubble* status_bubble, + ChromeViews::HWNDViewContainer* popup) + : Animation(kFramerate, this), + status_bubble_(status_bubble), + popup_(popup), + stage_(BUBBLE_HIDDEN), + style_(STYLE_STANDARD), + timer_factory_(this), + opacity_start_(0), + opacity_end_(0) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font(rb.GetFont(ResourceBundle::BaseFont)); + SetFont(font); + } + + ~StatusView() { + Stop(); + CancelTimer(); + } + + // The bubble can be in one of many stages: + typedef enum BubbleStage { + BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN. + BUBBLE_HIDING_FADE, // In a fade-out transition. + BUBBLE_HIDING_TIMER, // Waiting before a fade-out. + BUBBLE_SHOWING_TIMER, // Waiting before a fade-in. + BUBBLE_SHOWING_FADE, // In a fade-in transition. + BUBBLE_SHOWN // Fully visible. + }; + + typedef enum BubbleStyle { + STYLE_BOTTOM, + STYLE_FLOATING, + STYLE_STANDARD + }; + + // Set the bubble text to a certain value, hides the bubble if text is + // an empty string. + void SetText(const std::wstring& text); + + BubbleStage GetState() const { return stage_; } + + void SetStyle(BubbleStyle style); + + // Show the bubble instantly. + void Show(); + + // Hide the bubble instantly. + void Hide(); + + // Resets any timers we have. Typically called when the user moves a + // mouse. + void ResetTimer(); + + private: + class InitialTimer; + + // Manage the timers that control the delay before a fade begins or ends. + void StartTimer(int time); + void OnTimer(); + void CancelTimer(); + void RestartTimer(int delay); + + // Manage the fades and starting and stopping the animations correctly. + void StartFade(double start, double end, int duration); + void StartHiding(); + void StartShowing(); + + // Animation functions. + double GetCurrentOpacity(); + void SetOpacity(double opacity); + void AnimateToState(double state); + void AnimationEnded(const Animation* animation); + + virtual void Paint(ChromeCanvas* canvas); + + BubbleStage stage_; + BubbleStyle style_; + + ScopedRunnableMethodFactory<StatusBubble::StatusView> timer_factory_; + + // Manager, owns us. + StatusBubble* status_bubble_; + + // Handle to the HWND that contains us. + ChromeViews::HWNDViewContainer* popup_; + + // The currently-displayed text. + std::wstring text_; + + // Start and end opacities for the current transition - note that as a + // fade-in can easily turn into a fade out, opacity_start_ is sometimes + // a value between 0 and 1. + double opacity_start_; + double opacity_end_; +}; + +void StatusBubble::StatusView::SetText(const std::wstring& text) { + if (text.empty()) { + // The string was empty. + StartHiding(); + } else { + // We want to show the string. + text_ = text; + StartShowing(); + } + + SchedulePaint(); +} + +void StatusBubble::StatusView::Show() { + Stop(); + CancelTimer(); + SetOpacity(1.0); + stage_ = BUBBLE_SHOWN; + PaintNow(); +} + +void StatusBubble::StatusView::Hide() { + Stop(); + CancelTimer(); + SetOpacity(0.0); + text_.clear(); + stage_ = BUBBLE_HIDDEN; +} + +void StatusBubble::StatusView::StartTimer(int time) { + if (!timer_factory_.empty()) + timer_factory_.RevokeAll(); + + MessageLoop::current()->PostDelayedTask(FROM_HERE, + timer_factory_.NewRunnableMethod(&StatusBubble::StatusView::OnTimer), + time); +} + +void StatusBubble::StatusView::OnTimer() { + if (stage_ == BUBBLE_HIDING_TIMER) { + stage_ = BUBBLE_HIDING_FADE; + StartFade(1.0, 0.0, kHideFadeDurationMS); + } else if (stage_ == BUBBLE_SHOWING_TIMER) { + stage_ = BUBBLE_SHOWING_FADE; + StartFade(0.0, 1.0, kShowFadeDurationMS); + } +} + +void StatusBubble::StatusView::CancelTimer() { + if (!timer_factory_.empty()) { + timer_factory_.RevokeAll(); + } +} + +void StatusBubble::StatusView::RestartTimer(int delay) { + CancelTimer(); + StartTimer(delay); +} + +void StatusBubble::StatusView::ResetTimer() { + if (stage_ == BUBBLE_SHOWING_TIMER) { + // We hadn't yet begun showing anything when we received a new request + // for something to show, so we start from scratch. + RestartTimer(kShowDelay); + } +} + +void StatusBubble::StatusView::StartFade(double start, + double end, + int duration) { + opacity_start_ = start; + opacity_end_ = end; + + // This will also reset the currently-occuring animation. + SetDuration(duration); + Start(); +} + +void StatusBubble::StatusView::StartHiding() { + if (stage_ == BUBBLE_SHOWN) { + stage_ = BUBBLE_HIDING_TIMER; + StartTimer(kHideDelay); + } else if (stage_ == BUBBLE_SHOWING_TIMER) { + stage_ = BUBBLE_HIDDEN; + CancelTimer(); + } else if (stage_ == BUBBLE_SHOWING_FADE) { + stage_ = BUBBLE_HIDING_FADE; + // Figure out where we are in the current fade. + double current_opacity = GetCurrentOpacity(); + + // Start a fade in the opposite direction. + StartFade(current_opacity, 0.0, + static_cast<int>(kHideFadeDurationMS * current_opacity)); + } +} + +void StatusBubble::StatusView::StartShowing() { + if (stage_ == BUBBLE_HIDDEN) { + stage_ = BUBBLE_SHOWING_TIMER; + StartTimer(kShowDelay); + } else if (stage_ == BUBBLE_HIDING_TIMER) { + stage_ = BUBBLE_SHOWN; + CancelTimer(); + } else if (stage_ == BUBBLE_HIDING_FADE) { + // We're partway through a fade. + stage_ = BUBBLE_SHOWING_FADE; + + // Figure out where we are in the current fade. + double current_opacity = GetCurrentOpacity(); + + // Start a fade in the opposite direction. + StartFade(current_opacity, 1.0, + static_cast<int>(kShowFadeDurationMS * current_opacity)); + } else if (stage_ == BUBBLE_SHOWING_TIMER) { + // We hadn't yet begun showing anything when we received a new request + // for something to show, so we start from scratch. + ResetTimer(); + } +} + +// Animation functions. +double StatusBubble::StatusView::GetCurrentOpacity() { + return opacity_start_ + (opacity_end_ - opacity_start_) * + Animation::GetCurrentValue(); +} + +void StatusBubble::StatusView::SetOpacity(double opacity) { + popup_->SetLayeredAlpha(static_cast<BYTE>(opacity * 255)); + SchedulePaint(); +} + +void StatusBubble::StatusView::AnimateToState(double state) { + SetOpacity(GetCurrentOpacity()); +} + +void StatusBubble::StatusView::AnimationEnded( + const Animation* animation) { + SetOpacity(opacity_end_); + + if (stage_ == BUBBLE_HIDING_FADE) { + stage_ = BUBBLE_HIDDEN; + } else if (stage_ == BUBBLE_SHOWING_FADE) { + stage_ = BUBBLE_SHOWN; + } +} + +void StatusBubble::StatusView::SetStyle(BubbleStyle style) { + if (style_ != style) { + style_ = style; + SchedulePaint(); + } +} + +void StatusBubble::StatusView::Paint(ChromeCanvas* canvas) { + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setFlags(SkPaint::kAntiAlias_Flag); + paint.setColor(kBubbleColor); + + RECT parent_rect; + ::GetWindowRect(popup_->GetHWND(), &parent_rect); + + // Draw our background. + SkRect rect; + int width = parent_rect.right - parent_rect.left; + int height = parent_rect.bottom - parent_rect.top; + + // Figure out how to round the bubble's four corners. + SkScalar rad[8]; + + // Top Edges - if the bubble is in its bottom position (sticking downwards), + // then we square the top edges. Otherwise, we square the edges based on the + // position of the bubble within the window (the bubble is positioned in the + // southeast corner in RTL and in the southwest conver in LTR). + if (style_ == STYLE_BOTTOM) { + // Top Left corner. + rad[0] = 0; + rad[1] = 0; + + // Top Right corner. + rad[2] = 0; + rad[3] = 0; + } else { + if (UILayoutIsRightToLeft()) { + // Top Left corner. + rad[0] = SkIntToScalar(kBubbleCornerRadius); + rad[1] = SkIntToScalar(kBubbleCornerRadius); + + // Top Right corner. + rad[2] = 0; + rad[3] = 0; + } else { + // Top Left corner. + rad[0] = 0; + rad[1] = 0; + + // Top Right corner. + rad[2] = SkIntToScalar(kBubbleCornerRadius); + rad[3] = SkIntToScalar(kBubbleCornerRadius); + } + } + + // Bottom edges - square these off if the bubble is in its standard position + // (sticking upward). + if (style_ == STYLE_STANDARD) { + // Bottom Right Corner. + rad[4] = 0; + rad[5] = 0; + + // Bottom Left Corner. + rad[6] = 0; + rad[7] = 0; + } else { + // Bottom Right Corner. + rad[4] = SkIntToScalar(kBubbleCornerRadius); + rad[5] = SkIntToScalar(kBubbleCornerRadius); + + // Bottom Left Corner. + rad[6] = SkIntToScalar(kBubbleCornerRadius); + rad[7] = SkIntToScalar(kBubbleCornerRadius); + } + + // Draw the bubble's shadow. + SkPaint shadow_paint; + shadow_paint.setFlags(SkPaint::kAntiAlias_Flag); + shadow_paint.setColor(kShadowColor); + + rect.set(0, 0, + SkIntToScalar(width), + SkIntToScalar(height)); + + SkPath shadow_path; + shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction); + canvas->drawPath(shadow_path, shadow_paint); + + // Draw the bubble. + SkPath path; + rect.set(SkIntToScalar(kShadowSize), + SkIntToScalar(kShadowSize), + SkIntToScalar(width - kShadowSize), + SkIntToScalar(height - kShadowSize)); + + path.addRoundRect(rect, rad, SkPath::kCW_Direction); + canvas->drawPath(path, paint); + + + int text_width = std::min(static_cast<int>(parent_rect.right - + parent_rect.left - kTextPositionX - + kTextPadding), + static_cast<int>(ChromeViews::Label::GetFont() + .GetStringWidth(text_))); + + // Draw highlight text and then the text body. In order to make sure the text + // is aligned to the right on RTL UIs, we mirror the text bounds if the + // locale is RTL. + gfx::Rect body_bounds(kTextPositionX, + kTextPositionY, + text_width, + parent_rect.bottom - parent_rect.top); + body_bounds.set_x(MirroredLeftPointForRect(body_bounds)); + canvas->DrawStringInt(text_, + ChromeViews::Label::GetFont(), + kTextHighlightColor, + body_bounds.x() + 1, + body_bounds.y() + 1, + body_bounds.width(), + body_bounds.height()); + + canvas->DrawStringInt(text_, + ChromeViews::Label::GetFont(), + kTextColor, + body_bounds.x(), + body_bounds.y(), + body_bounds.width(), + body_bounds.height()); +} + +// StatusBubble --------------------------------------------------------------- + +StatusBubble::StatusBubble(ChromeViews::ViewContainer* frame) + : popup_(NULL), + frame_(frame), + view_(NULL), + opacity_(0), + position_(0, 0), + size_(0, 0), + offset_(0) { +} + +StatusBubble::~StatusBubble() { + if (popup_) { + popup_->CloseNow(); + } + + popup_ = NULL; + position_ = NULL; + size_ = NULL; +} + +void StatusBubble::Init() { + if (!popup_) { + popup_ = new ChromeViews::HWNDViewContainer(); + popup_->set_delete_on_destroy(false); + + if (!view_) { + view_ = new StatusView(this, popup_); + } + + gfx::Rect rc(0, 0, 0, 0); + + popup_->set_window_style(WS_POPUP); + popup_->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW | + WS_EX_TRANSPARENT | + l10n_util::GetExtendedTooltipStyles()); + popup_->SetLayeredAlpha(0x00); + popup_->Init(frame_->GetHWND(), rc, view_, + false); + Reposition(); + popup_->ShowWindow(SW_SHOWNOACTIVATE); + } +} + +void StatusBubble::SetStatus(const std::wstring& status_text) { + if (status_text_ == status_text) + return; + + Init(); + status_text_ = status_text; + if (!status_text_.empty()) { + view_->SetText(status_text); + view_->Show(); + } else if (!url_text_.empty()) { + view_->SetText(url_text_); + } else { + view_->SetText(std::wstring()); + } +} + +void StatusBubble::SetURL(const GURL& url, const std::wstring& languages) { + Init(); + + // If we want to clear a displayed URL but there is a status still to + // display, display that status instead. + if (url.is_empty() && !status_text_.empty()) { + url_text_ = std::wstring(); + view_->SetText(status_text_); + return; + } + + // Set Elided Text correspoding to the GURL object. + RECT parent_rect; + ::GetWindowRect(popup_->GetHWND(), &parent_rect); + int text_width = static_cast<int>(parent_rect.right - + parent_rect.left - kTextPositionX - + kTextPadding); + url_text_ = gfx::ElideUrl(url, view_->Label::GetFont(), text_width, + languages); + + // An URL is always treated as a left-to-right string. On right-to-left UIs + // we need to explicitly mark the URL as LTR to make sure it is displayed + // correctly. + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT && + !url_text_.empty()) + l10n_util::WrapStringWithLTRFormatting(&url_text_); + view_->SetText(url_text_); +} + +void StatusBubble::ClearURL() { + Init(); + url_text_ = std::wstring(); + view_->SetText(url_text_); +} + +void StatusBubble::Hide() { + status_text_ = std::wstring(); + url_text_ = std::wstring(); + if (view_) { + view_->Hide(); + } +} + +void StatusBubble::MouseMoved() { + if (view_) { + view_->ResetTimer(); + + if (view_->GetState() != StatusView::BUBBLE_HIDDEN && + view_->GetState() != StatusView::BUBBLE_HIDING_FADE && + view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) { + AvoidMouse(); + } + } +} + +void StatusBubble::AvoidMouse() { + // Our status bubble is located in screen coordinates, so we should get + // those rather than attempting to reverse decode the web contents + // coordinates. + CPoint cursor_location; + GetCursorPos(&cursor_location); + + // Get the position of the frame. + CPoint top_left(0, 0); + ChromeViews::View::ConvertPointToScreen(frame_->GetRootView(), &top_left); + + // Get the cursor position relative to the popup. + cursor_location.x -= (top_left.x + position_.x); + cursor_location.y -= (top_left.y + position_.y); + + // If the mouse is in a position where we think it would move the + // status bubble, figure out where and how the bubble should be moved. + if (cursor_location.y > -kMousePadding && + cursor_location.x < size_.cx + kMousePadding) { + int offset = kMousePadding + cursor_location.y; + + // Make the movement non-linear. + offset = offset * offset / kMousePadding; + + // When the mouse is entering from the right, we want the offset to be + // scaled by how horizontally far away the cursor is from the bubble. + if (cursor_location.x > size_.cx) { + offset = static_cast<int>(static_cast<float>(offset) * ( + static_cast<float>(kMousePadding - + (cursor_location.x - size_.cx)) / + static_cast<float>(kMousePadding))); + } + + // Cap the offset and change the visual presentation of the bubble + // depending on where it ends up (so that rounded corners square off + // and mate to the edges of the tab content). + if (offset >= size_.cy - kShadowSize * 2) { + offset = size_.cy - kShadowSize * 2; + view_->SetStyle(StatusView::STYLE_BOTTOM); + } else if (offset > kBubbleCornerRadius / 2 - kShadowSize) { + view_->SetStyle(StatusView::STYLE_FLOATING); + } else { + view_->SetStyle(StatusView::STYLE_STANDARD); + } + + offset_ = offset; + popup_->MoveWindow(top_left.x + position_.x, + top_left.y + position_.y + offset_, + size_.cx, + size_.cy); + } else if (offset_ != 0) { + offset_ = 0; + view_->SetStyle(StatusView::STYLE_STANDARD); + popup_->MoveWindow(top_left.x + position_.x, + top_left.y + position_.y, + size_.cx, + size_.cy); + } +} + +void StatusBubble::Reposition() { + if (popup_) { + CPoint top_left(0, 0); + ChromeViews::View::ConvertPointToScreen(frame_->GetRootView(), &top_left); + + popup_->MoveWindow(top_left.x + position_.x, + top_left.y + position_.y, + size_.cx, + size_.cy); + } +} + +void StatusBubble::SetBounds(int x, int y, int w, int h) { + // If the UI layout is RTL, we need to mirror the position of the bubble + // relative to the parent. + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) { + CRect frame_bounds; + frame_->GetBounds(&frame_bounds, false); + int mirrored_x = frame_bounds.Width() - x - w; + position_.SetPoint(mirrored_x, y); + } else { + position_.SetPoint(x, y); + } + + size_.SetSize(w, h); + Reposition(); +} diff --git a/chrome/browser/views/status_bubble.h b/chrome/browser/views/status_bubble.h new file mode 100644 index 0000000..ee9edc6 --- /dev/null +++ b/chrome/browser/views/status_bubble.h @@ -0,0 +1,110 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_STATUS_BUBBLE_H__ +#define CHROME_BROWSER_VIEWS_STATUS_BUBBLE_H__ + +#include "base/gfx/rect.h" +#include "chrome/views/hwnd_view_container.h" +#include "chrome/views/view_container.h" +#include "googleurl/src/gurl.h" + +// StatusBubble displays a bubble of text that fades in, hovers over the +// browser chrome and fades away when not needed. It is primarily designed +// to allow users to see where hovered links point to. +class StatusBubble { + public: + explicit StatusBubble(ChromeViews::ViewContainer* frame); + ~StatusBubble(); + + // Sets the bubble contents to a specific string and causes the bubble + // to display immediately. Subsequent empty SetURL calls (typically called + // when the cursor exits a link) will set the status bubble back to its + // status text. To hide the status bubble again, either call SetStatus + // with an empty string, or call Hide(). + void SetStatus(const std::wstring& status); + + // Sets the bubble text to a URL - if given a non-empty URL, this will cause + // the bubble to fade in and remain open until given an empty URL or until + // the Hide() method is called. languages is the value of Accept-Language + // to determine what characters are understood by a user. + void SetURL(const GURL& url, const std::wstring& languages); + + // Clear the URL and begin the fadeout of the bubble if not status text + // needs to be displayed. + void ClearURL(); + + // Skip the fade and instant-hide the bubble. + void Hide(); + + // Called when the user's mouse has moved over web content. + void MouseMoved(); + + // Set the bounds of the bubble relative to the browser window. + void SetBounds(int x, int y, int w, int h); + + // Reposition the bubble - as we are using a WS_POPUP for the bubble, + // we have to manually position it when the browser window moves. + void Reposition(); + + private: + class StatusView; + + // Initializes the popup and view. + void Init(); + + // Attempt to move the status bubble out of the way of the cursor, allowing + // users to see links in the region normally occupied by the status bubble. + void AvoidMouse(); + + // The status text we want to display when there are no URLs to display. + std::wstring status_text_; + + // The url we want to display when there is not status text to display. + std::wstring url_text_; + + // Position relative to the parent window. + CPoint position_; + CSize size_; + + // How vertically offset the bubble is from its root position_. + int offset_; + + // We use a HWND for the popup so that it may float above any HWNDs in our + // UI (the location bar, for example). + ChromeViews::HWNDViewContainer* popup_; + double opacity_; + + ChromeViews::ViewContainer* frame_; + StatusView* view_; + + DISALLOW_EVIL_CONSTRUCTORS(StatusBubble); +}; + +#endif // CHROME_BROWSER_VIEWS_STATUS_BUBBLE_H__ diff --git a/chrome/browser/views/tab_icon_view.cc b/chrome/browser/views/tab_icon_view.cc new file mode 100644 index 0000000..a6ac4c7 --- /dev/null +++ b/chrome/browser/views/tab_icon_view.cc @@ -0,0 +1,141 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/favicon_size.h" +#include "chrome/common/gfx/icon_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/browser/views/tab_icon_view.h" +#include "chrome/app/chrome_dll_resource.h" + +static bool g_initialized = false; +static SkBitmap* g_default_fav_icon = NULL; +static SkBitmap* g_throbber_frames = NULL; +static SkBitmap* g_throbber_frames_light = NULL; +static int g_throbber_frame_count; + +// static +void TabIconView::InitializeIfNeeded() { + if (!g_initialized) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + g_default_fav_icon = rb.GetBitmapNamed(IDR_DEFAULT_FAVICON); + g_throbber_frames = rb.GetBitmapNamed(IDR_THROBBER); + g_throbber_frames_light = rb.GetBitmapNamed(IDR_THROBBER_LIGHT); + g_throbber_frame_count = g_throbber_frames->width() / + g_throbber_frames->height(); + + // Verify that our light and dark styles have the same number of frames. + DCHECK(g_throbber_frame_count == + g_throbber_frames_light->width() / g_throbber_frames_light->height()); + g_initialized = true; + } +} + +TabIconView::TabIconView(TabContentsProvider* provider) + : provider_(provider), + is_light_(false), + throbber_running_(false), + throbber_frame_(0) { + InitializeIfNeeded(); +} + +TabIconView::~TabIconView() { +} + +void TabIconView::Update() { + TabContents* contents = provider_->GetCurrentTabContents(); + if (throbber_running_) { + // We think the tab is loading. + if (!contents || !contents->is_loading()) { + // Woops, tab is invalid or not loading, reset our status and schedule + // a paint. + throbber_running_ = false; + SchedulePaint(); + } else { + // The tab is still loading, increment the frame. + throbber_frame_ = (throbber_frame_ + 1) % g_throbber_frame_count; + SchedulePaint(); + } + } else if (contents && contents->is_loading()) { + // We didn't think we were loading, but the tab is loading. Reset the + // frame and status and schedule a paint. + throbber_running_ = true; + throbber_frame_ = 0; + SchedulePaint(); + } +} + +void TabIconView::PaintThrobber(ChromeCanvas* canvas) { + int image_size = g_throbber_frames->height(); + int image_offset = throbber_frame_ * image_size; + canvas->DrawBitmapInt(is_light_ ? *g_throbber_frames_light : + *g_throbber_frames, + image_offset, 0, image_size, image_size, + 0, 0, image_size, image_size, false); +} + +void TabIconView::PaintFavIcon(ChromeCanvas* canvas, const SkBitmap& bitmap) { + int bw = bitmap.width(); + int bh = bitmap.height(); + if (bw <= kFavIconSize && bh <= kFavIconSize) { + canvas->DrawBitmapInt(bitmap, (GetWidth() - kFavIconSize) / 2, + (GetHeight() - kFavIconSize) / 2); + } else { + canvas->DrawBitmapInt(bitmap, 0, 0, bw, bh, 0, 0, GetWidth(), GetHeight(), + true); + } +} + +void TabIconView::Paint(ChromeCanvas* canvas) { + TabContents* contents = provider_->GetCurrentTabContents(); + bool rendered = false; + + if (contents) { + if (throbber_running_) { + rendered = true; + PaintThrobber(canvas); + } else { + SkBitmap favicon = provider_->GetFavIcon(); + if (!favicon.isNull()) { + rendered = true; + PaintFavIcon(canvas, favicon); + } + } + } + + if (!rendered) { + PaintFavIcon(canvas, *g_default_fav_icon); + } +} + +void TabIconView::GetPreferredSize(CSize* out) { + out->cx = out->cy = kFavIconSize; +} diff --git a/chrome/browser/views/tab_icon_view.h b/chrome/browser/views/tab_icon_view.h new file mode 100644 index 0000000..318d629 --- /dev/null +++ b/chrome/browser/views/tab_icon_view.h @@ -0,0 +1,89 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEW_TAB_ICON_VIEW_H__ +#define CHROME_BROWSER_VIEW_TAB_ICON_VIEW_H__ + +#include "chrome/views/view.h" + +class TabContents; + +//////////////////////////////////////////////////////////////////////////////// +// +// A view to display a tab fav icon or a throbber. +// +//////////////////////////////////////////////////////////////////////////////// +class TabIconView : public ChromeViews::View { + public: + class TabContentsProvider { + public: + // Should return the current tab contents this TabIconView object is + // representing. + virtual TabContents* GetCurrentTabContents() = 0; + + // Returns the favicon to display in the icon view + virtual SkBitmap GetFavIcon() = 0; + }; + + static void InitializeIfNeeded(); + + explicit TabIconView(TabContentsProvider* provider); + virtual ~TabIconView(); + + // Invoke whenever the tab state changes or the throbber should update. + void Update(); + + // Set the throbber to the light style (for use on dark backgrounds). + void set_is_light(bool is_light) { is_light_ = is_light; } + + // Overriden from View + virtual void Paint(ChromeCanvas* canvas); + virtual void GetPreferredSize(CSize* out); + + private: + void PaintThrobber(ChromeCanvas* canvas); + void PaintFavIcon(ChromeCanvas* canvas, const SkBitmap& bitmap); + + // Our provider of current tab contents. + TabContentsProvider* provider_; + + // Whether the throbber is running. + bool throbber_running_; + + // Whether we should display our light or dark style. + bool is_light_; + + // Current frame of the throbber being painted. This is only used if + // throbber_running_ is true. + int throbber_frame_; + + DISALLOW_EVIL_CONSTRUCTORS(TabIconView); +}; + +#endif // CHROME_BROWSER_VIEW_TAB_ICON_VIEW_H__ diff --git a/chrome/browser/views/theme_helpers.cc b/chrome/browser/views/theme_helpers.cc new file mode 100644 index 0000000..fa2820d --- /dev/null +++ b/chrome/browser/views/theme_helpers.cc @@ -0,0 +1,115 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/theme_helpers.h" + +#include <atlbase.h> +#include <atlapp.h> +#include <atltheme.h> + +#include "base/gfx/bitmap_platform_device.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "base/logging.h" +#include "SkGradientShader.h" + +void GetRebarGradientColors(int width, int x1, int x2, SkColor* c1, SkColor* c2) { + DCHECK(c1 && c2) << "ThemeHelpers::GetRebarGradientColors - c1 or c2 is NULL!"; + + // To get the colors we need, we draw a horizontal gradient using + // DrawThemeBackground, then extract the pixel values from and return + // those so calling code can use them to create gradient brushes for use in + // rendering in other directions. + + ChromeCanvas canvas(width, 1, true); + + // Render the Rebar gradient into the DIB + CTheme theme; + if (theme.IsThemingSupported()) + theme.OpenThemeData(NULL, L"REBAR"); + // On Windows XP+, if using a Theme, we can ask the theme to render the + // gradient for us. + if (!theme.IsThemeNull()) { + HDC dc = canvas.beginPlatformPaint(); + RECT rect = { 0, 0, width, 1 }; + theme.DrawThemeBackground(dc, 0, 0, &rect, NULL); + canvas.endPlatformPaint(); + } else { + // On Windows 2000 or Windows XP+ with the Classic theme selected, we need + // to build our own gradient using system colors. + SkColor grad_colors[2]; + COLORREF hl_ref = ::GetSysColor(COLOR_3DHILIGHT); + grad_colors[0] = SkColorSetRGB(GetRValue(hl_ref), GetGValue(hl_ref), + GetBValue(hl_ref)); + COLORREF face_ref = ::GetSysColor(COLOR_3DFACE); + grad_colors[1] = SkColorSetRGB(GetRValue(face_ref), GetGValue(face_ref), + GetBValue(face_ref)); + SkPoint grad_points[2]; + grad_points[0].set(SkIntToScalar(0), SkIntToScalar(0)); + grad_points[1].set(SkIntToScalar(width), SkIntToScalar(0)); + SkShader* gradient_shader = SkGradientShader::CreateLinear( + grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode); + SkPaint paint; + paint.setShader(gradient_shader); + // Shader created with a ref count of 1, release as the paint now owns + // the gradient. + gradient_shader->unref(); + paint.setStyle(SkPaint::kFill_Style); + canvas.drawRectCoords(SkIntToScalar(0), SkIntToScalar(0), + SkIntToScalar(width), SkIntToScalar(1), paint); + } + + // Extract the color values from the selected pixels + // The | in the following operations forces the alpha to 0xFF. This is + // needed as windows sets the alpha to 0 when it renders. + gfx::BitmapPlatformDevice& device = + static_cast<gfx::BitmapPlatformDevice&>(canvas.getTopPlatformDevice()); + *c1 = 0xFF000000 | device.getColorAt(x1, 0); + *c2 = 0xFF000000 | device.getColorAt(x2, 0); +} + +void GetDarkLineColor(SkColor* dark_color) { + DCHECK(dark_color) << "ThemeHelpers::DarkColor - dark_color is NULL!"; + + CTheme theme; + if (theme.IsThemingSupported()) + theme.OpenThemeData(NULL, L"REBAR"); + + // Note: the alpha values were chosen scientifically according to what looked + // best to me at the time! --beng + if (!theme.IsThemeNull()) { + *dark_color = SkColorSetARGB(60, 0, 0, 0); + } else { + COLORREF shadow_ref = ::GetSysColor(COLOR_3DSHADOW); + *dark_color = SkColorSetARGB(175, + GetRValue(shadow_ref), + GetGValue(shadow_ref), + GetBValue(shadow_ref)); + } +} + diff --git a/chrome/browser/views/theme_helpers.h b/chrome/browser/views/theme_helpers.h new file mode 100644 index 0000000..9704b28 --- /dev/null +++ b/chrome/browser/views/theme_helpers.h @@ -0,0 +1,53 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_THEME_HELPERS_H__ +#define CHROME_BROWSER_VIEWS_THEME_HELPERS_H__ + +#include <windows.h> + +#include "SkColor.h" + +// Get the colors at two points on a Rebar background gradient. This is for +// drawing Rebar like backgrounds in Views. The reason not to just use +// DrawThemeBackground is that it only draws horizontally, but by extracting +// the colors at two points on the X axis of a background drawn +// by DrawThemeBackground, we can construct a LinearGradientBrush and draw +// such a gradient in any direction. +// +// The width parameter is the width of horizontal gradient that will be +// created to calculate the two colors. x1 and x2 are the two pixel positions +// along the X axis. +void GetRebarGradientColors(int width, int x1, int x2, SkColor* c1, SkColor* c2); + + +// Gets the color used to draw dark (inset beveled) lines. +void GetDarkLineColor(SkColor* dark_color); + +#endif // #ifndef CHROME_BROWSER_VIEWS_THEME_HELPERS_H__ diff --git a/chrome/browser/views/toolbar_star_toggle.cc b/chrome/browser/views/toolbar_star_toggle.cc new file mode 100644 index 0000000..36a4bb2 --- /dev/null +++ b/chrome/browser/views/toolbar_star_toggle.cc @@ -0,0 +1,116 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/toolbar_star_toggle.h" + +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/browser/views/bookmark_bubble_view.h" +#include "chrome/browser/views/toolbar_view.h" +#include "chrome/common/resource_bundle.h" +#include "googleurl/src/gurl.h" + +// The amount of time (in milliseconds) between when the bubble closes and when +// pressing on the button again does something. Yes, this is a hackish. I tried +// many different options, all to no avail: +// . Keying off mouse activation: this didn't work as there is no way to know +// which window receives the activation. Additionally once the mouse +// activation occurs we have no way to tie the next mouse event to the mouse +// activation. +// . Watching all events as we dispatch them in the MessageLoop. Mouse +// activation isn't an observable event though. +// Ideally we could use mouse capture for this, but we can't use mouse capture +// with the bubble because it has other native windows. +static const int64 kDisallowClickMS = 40; + +ToolbarStarToggle::ToolbarStarToggle(BrowserToolbarView* host) + : host_(host), + ignore_click_(false), + is_bubble_showing_(false) { +} + +void ToolbarStarToggle::ShowStarBubble(const GURL& url, bool newly_bookmarked) { + if (is_bubble_showing_) { + // Don't show if we're already showing the bubble. + return; + } + + CPoint star_location(0, 0); + ChromeViews::View::ConvertPointToScreen(this, &star_location); + // Shift the x location by 1 as visually the center of the star appears 1 + // pixel to the right. By doing this bubble arrow points to the center + // of the star. + gfx::Rect star_bounds(star_location.x + 1, star_location.y, GetWidth(), + GetHeight()); + BookmarkBubbleView::Show(host_->browser()->GetTopLevelHWND(), star_bounds, + this, host_->profile(), url, newly_bookmarked); + is_bubble_showing_ = true; +} + +bool ToolbarStarToggle::OnMousePressed(const ChromeViews::MouseEvent& e) { + ignore_click_ = ((TimeTicks::Now() - bubble_closed_time_).InMilliseconds() < + kDisallowClickMS); + return ToggleButton::OnMousePressed(e); +} + +void ToolbarStarToggle::OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled) { + ToggleButton::OnMouseReleased(e, canceled); + ignore_click_ = false; +} + +void ToolbarStarToggle::OnDragDone() { + ToggleButton::OnDragDone(); + ignore_click_ = false; +} + +void ToolbarStarToggle::NotifyClick(int mouse_event_flags) { + if (!ignore_click_ && !is_bubble_showing_) + ToggleButton::NotifyClick(mouse_event_flags); +} + +SkBitmap ToolbarStarToggle::GetImageToPaint() { + if (is_bubble_showing_) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + return *rb.GetBitmapNamed(IDR_STARRED_P); + } + return Button::GetImageToPaint(); +} + +void ToolbarStarToggle::InfoBubbleClosing(InfoBubble* info_bubble) { + is_bubble_showing_ = false; + SchedulePaint(); + bubble_closed_time_ = TimeTicks::Now(); +} + +bool ToolbarStarToggle::CloseOnEscape() { + return true; +} diff --git a/chrome/browser/views/toolbar_star_toggle.h b/chrome/browser/views/toolbar_star_toggle.h new file mode 100644 index 0000000..f76b5fa --- /dev/null +++ b/chrome/browser/views/toolbar_star_toggle.h @@ -0,0 +1,91 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_TOOLBAR_STAR_TOGGLE_H_ +#define CHROME_BROWSER_VIEWS_TOOLBAR_STAR_TOGGLE_H_ + +#include "base/time.h" +#include "chrome/browser/views/info_bubble.h" +#include "chrome/views/button.h" + +class BrowserToolbarView; +class GURL; + +// ToolbarStarToggle is used for the star button on the toolbar, allowing the +// user to star the current page. ToolbarStarToggle manages showing the +// InfoBubble and rendering the appropriate state while the bubble is visible. + +class ToolbarStarToggle : public ChromeViews::ToggleButton, + public InfoBubbleDelegate { + public: + explicit ToolbarStarToggle(BrowserToolbarView* host); + + // If the bubble isn't showing, shows it. + void ShowStarBubble(const GURL& url, bool newly_bookmarked); + + bool is_bubble_showing() const { return is_bubble_showing_; } + + // Overridden to update ignore_click_ based on whether the mouse was clicked + // quickly after the bubble was hidden. + virtual bool OnMousePressed(const ChromeViews::MouseEvent& e); + + // Overridden to set ignore_click_ to false. + virtual void OnMouseReleased(const ChromeViews::MouseEvent& e, + bool canceled); + virtual void OnDragDone(); + + // Only invokes super if ignore_click_ is true and the bubble isn't showing. + virtual void NotifyClick(int mouse_event_flags); + + protected: + // Overridden to so that we appear pressed while the bubble is showing. + virtual SkBitmap GetImageToPaint(); + + private: + // InfoBubbleDelegate. + virtual void InfoBubbleClosing(InfoBubble* info_bubble); + virtual bool CloseOnEscape(); + + // Contains us. + BrowserToolbarView* host_; + + // Time the bubble last closed. + TimeTicks bubble_closed_time_; + + // If true NotifyClick does nothing. This is set in OnMousePressed based on + // the amount of time between when the bubble clicked and now. + bool ignore_click_; + + // Is the bubble showing? + bool is_bubble_showing_; + + DISALLOW_EVIL_CONSTRUCTORS(ToolbarStarToggle); +}; + +#endif // CHROME_BROWSER_VIEWS_TOOLBAR_STAR_TOGGLE_H_ diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc new file mode 100644 index 0000000..7b673a3 --- /dev/null +++ b/chrome/browser/views/toolbar_view.cc @@ -0,0 +1,675 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/toolbar_view.h" + +#include <string> + +#include "base/logging.h" +#include "base/path_service.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/user_metrics.h" +#include "chrome/browser/views/dom_view.h" +#include "chrome/browser/views/go_button.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/browser/views/theme_helpers.h" +#include "chrome/browser/views/toolbar_star_toggle.h" +#include "chrome/browser/view_ids.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/button_dropdown.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/view_container.h" +#include "chrome/views/background.h" +#include "chrome/views/label.h" +#include "net/base/net_util.h" + +#include "generated_resources.h" + +static const int kControlHorizOffset = 4; +static const int kControlVertOffset = 6; +static const int kControlIndent = 3; +static const int kStatusBubbleWidth = 480; + +// Separation between the location bar and the menus. +static const int kMenuButtonOffset = 3; + +// Padding to the right of the location bar +static const int kPaddingRight = 2; + +BrowserToolbarView::BrowserToolbarView(CommandController* controller, + Browser* browser) + : EncodingMenuControllerDelegate(browser, controller), + controller_(controller), + model_(browser->toolbar_model()), + back_(NULL), + forward_(NULL), + reload_(NULL), + home_(NULL), + star_(NULL), + location_bar_(NULL), + go_(NULL), + profile_(NULL), + acc_focused_view_(NULL), + browser_(browser), + tab_(NULL) { + back_menu_model_.reset(new BackForwardMenuModel( + browser, BackForwardMenuModel::BACKWARD_MENU_DELEGATE)); + forward_menu_model_.reset(new BackForwardMenuModel( + browser, BackForwardMenuModel::FORWARD_MENU_DELEGATE)); +} + +BrowserToolbarView::~BrowserToolbarView() { +} + +void BrowserToolbarView::Init(Profile* profile) { + // Create all the individual Views in the Toolbar. + CreateLeftSideControls(); + CreateCenterStack(profile); + CreateRightSideControls(profile); + + show_home_button_.Init(prefs::kShowHomeButton, profile->GetPrefs(), this); + + SetProfile(profile); +} + +void BrowserToolbarView::SetProfile(Profile* profile) { + if (profile == profile_) + return; + + profile_ = profile; + location_bar_->SetProfile(profile); +} + +void BrowserToolbarView::CreateLeftSideControls() { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + + back_ = new ChromeViews::ButtonDropDown(back_menu_model_.get()); + back_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_BACK)); + back_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_BACK_H)); + back_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_BACK_P)); + back_->SetImage(ChromeViews::Button::BS_DISABLED, + rb.GetBitmapNamed(IDR_BACK_D)); + back_->SetTooltipText(l10n_util::GetString(IDS_TOOLTIP_BACK)); + back_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_BACK)); + back_->SetID(VIEW_ID_BACK_BUTTON); + AddChildView(back_); + controller_->AddManagedButton(back_, IDC_BACK); + + forward_ = new ChromeViews::ButtonDropDown(forward_menu_model_.get()); + forward_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_FORWARD)); + forward_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_FORWARD_H)); + forward_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_FORWARD_P)); + forward_->SetImage(ChromeViews::Button::BS_DISABLED, + rb.GetBitmapNamed(IDR_FORWARD_D)); + forward_->SetTooltipText(l10n_util::GetString(IDS_TOOLTIP_FORWARD)); + forward_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_FORWARD)); + forward_->SetID(VIEW_ID_FORWARD_BUTTON); + AddChildView(forward_); + controller_->AddManagedButton(forward_, IDC_FORWARD); + + reload_ = new ChromeViews::Button(); + reload_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_RELOAD)); + reload_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_RELOAD_H)); + reload_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_RELOAD_P)); + reload_->SetTooltipText(l10n_util::GetString(IDS_TOOLTIP_RELOAD)); + reload_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_RELOAD)); + reload_->SetID(VIEW_ID_RELOAD_BUTTON); + AddChildView(reload_); + controller_->AddManagedButton(reload_, IDC_RELOAD); + + home_ = new ChromeViews::Button(); + home_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_HOME)); + home_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_HOME_H)); + home_->SetImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_HOME_P)); + home_->SetTooltipText(l10n_util::GetString(IDS_TOOLTIP_HOME)); + home_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_HOME)); + AddChildView(home_); + controller_->AddManagedButton(home_, IDC_HOME); +} + +void BrowserToolbarView::CreateCenterStack(Profile *profile) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + + star_ = new ToolbarStarToggle(this); + star_->SetImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_STAR)); + star_->SetImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_STAR_H)); + star_->SetImage(ChromeViews::Button::BS_PUSHED, + rb .GetBitmapNamed(IDR_STAR_P)); + star_->SetImage(ChromeViews::Button::BS_DISABLED, + rb .GetBitmapNamed(IDR_STAR_D)); + star_->SetToggledImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_STARRED)); + star_->SetToggledImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_STARRED_H)); + star_->SetToggledImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_STARRED_P)); + star_->SetDragController(this); + star_->SetTooltipText(l10n_util::GetString(IDS_TOOLTIP_STAR)); + star_->SetToggledTooltipText(l10n_util::GetString(IDS_TOOLTIP_STARRED)); + star_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_STAR)); + star_->SetID(VIEW_ID_STAR_BUTTON); + AddChildView(star_); + controller_->AddManagedButton(star_, IDC_STAR); + + location_bar_ = new LocationBarView(profile, controller_, model_, + this, false); + AddChildView(location_bar_); + location_bar_->Init(); + + // The Go button. + go_ = new GoButton(location_bar_, controller_); + go_->SetImage(ChromeViews::Button::BS_NORMAL, rb.GetBitmapNamed(IDR_GO)); + go_->SetImage(ChromeViews::Button::BS_HOT, rb.GetBitmapNamed(IDR_GO_H)); + go_->SetImage(ChromeViews::Button::BS_PUSHED, rb.GetBitmapNamed(IDR_GO_P)); + go_->SetToggledImage(ChromeViews::Button::BS_NORMAL, + rb.GetBitmapNamed(IDR_STOP)); + go_->SetToggledImage(ChromeViews::Button::BS_HOT, + rb.GetBitmapNamed(IDR_STOP_H)); + go_->SetToggledImage(ChromeViews::Button::BS_PUSHED, + rb.GetBitmapNamed(IDR_STOP_P)); + go_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_GO)); + go_->SetID(VIEW_ID_GO_BUTTON); + AddChildView(go_); +} + +void BrowserToolbarView::Update(TabContents* tab, bool should_restore_state) { + tab_ = tab; + + if (!location_bar_) + return; + + location_bar_->Update(should_restore_state ? tab : NULL); +} + +void BrowserToolbarView::OnInputInProgress(bool in_progress) { + // The edit should make sure we're only notified when something changes. + DCHECK(model_->input_in_progress() != in_progress); + + model_->set_input_in_progress(in_progress); + location_bar_->Update(NULL); +} + +void BrowserToolbarView::CreateRightSideControls(Profile* profile) { + ResourceBundle &rb = ResourceBundle::GetSharedInstance(); + + page_menu_ = + new ChromeViews::MenuButton(std::wstring(), this, false); + + // We use different menu button images if the locale is right-to-left. + if (UILayoutIsRightToLeft()) + page_menu_->SetIcon(*rb.GetBitmapNamed(IDR_MENU_PAGE_RTL)); + else + page_menu_->SetIcon(*rb.GetBitmapNamed(IDR_MENU_PAGE)); + + page_menu_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_PAGE)); + page_menu_->SetTooltipText(l10n_util::GetString(IDS_PAGEMENU_TOOLTIP)); + page_menu_->SetID(VIEW_ID_PAGE_MENU); + AddChildView(page_menu_); + + app_menu_ = new ChromeViews::MenuButton(std::wstring(), this, false); + if (UILayoutIsRightToLeft()) + app_menu_->SetIcon(*rb.GetBitmapNamed(IDR_MENU_CHROME_RTL)); + else + app_menu_->SetIcon(*rb.GetBitmapNamed(IDR_MENU_CHROME)); + + app_menu_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_APP)); + app_menu_->SetTooltipText(l10n_util::GetStringF(IDS_APPMENU_TOOLTIP, + l10n_util::GetString(IDS_PRODUCT_NAME))); + app_menu_->SetID(VIEW_ID_APP_MENU); + AddChildView(app_menu_); +} + +void BrowserToolbarView::Layout() { + CSize sz; + + // If we have not been initialized yet just do nothing. + if (back_ == NULL) + return; + + back_->GetPreferredSize(&sz); + back_->SetBounds(kControlIndent, + kControlVertOffset, + sz.cx, + sz.cy); + + forward_->GetPreferredSize(&sz); + forward_->SetBounds(back_->GetX() + back_->GetWidth(), + kControlVertOffset, + sz.cx, + sz.cy); + + reload_->GetPreferredSize(&sz); + reload_->SetBounds(forward_->GetX() + forward_->GetWidth() + + kControlHorizOffset, + kControlVertOffset, + sz.cx, + sz.cy); + + int offset = 0; + if (show_home_button_.GetValue()) { + home_->GetPreferredSize(&sz); + offset = kControlHorizOffset; + } else { + sz = CSize(0, 0); + } + home_->SetBounds(reload_->GetX() + reload_->GetWidth() + offset, + kControlVertOffset, sz.cx, sz.cy); + + star_->GetPreferredSize(&sz); + star_->SetBounds(home_->GetX() + home_->GetWidth() + kControlHorizOffset, + kControlVertOffset, sz.cx, sz.cy); + + page_menu_->GetPreferredSize(&sz); + int right_side_width = sz.cx + kMenuButtonOffset; + + app_menu_->GetPreferredSize(&sz); + right_side_width += sz.cx + kPaddingRight; + + go_->GetPreferredSize(&sz); + int go_button_height = sz.cy; + + location_bar_->SetBounds(star_->GetX() + star_->GetWidth(), + kControlVertOffset, + GetWidth() - right_side_width - + (star_->GetX() + star_->GetWidth() + + sz.cx), // go preferred width + sz.cy); + + go_->SetBounds(location_bar_->GetX() + location_bar_->GetWidth(), + kControlVertOffset, + sz.cx, + sz.cy); + + // Make sure the Page menu never overlaps the location bar. + int page_x = go_->GetX() + go_->GetWidth() + kMenuButtonOffset; + page_menu_->GetPreferredSize(&sz); + page_menu_->SetBounds(page_x, kControlVertOffset, sz.cx, go_->GetHeight()); + app_menu_->GetPreferredSize(&sz); + app_menu_->SetBounds(page_menu_->GetX() + page_menu_->GetWidth(), + page_menu_->GetY(), sz.cx, go_->GetHeight()); +} + +void BrowserToolbarView::DidGainFocus() { + // Find first accessible child (-1 for start search at parent). + int first_acc_child = GetNextAccessibleViewIndex(-1, false); + + // No buttons enabled or visible. + if (first_acc_child == -1) + return; + + acc_focused_view_ = GetChildViewAt(first_acc_child); + + // Default focus is on the toolbar. + int view_index = VIEW_ID_TOOLBAR; + + // Set hot-tracking for child, and update focused_view for MSAA focus event. + if (acc_focused_view_) { + acc_focused_view_->SetHotTracked(true); + // Update focused_view with MSAA-adjusted child id. + view_index = acc_focused_view_->GetID(); + } + + HWND hwnd = GetViewContainer()->GetHWND(); + + // Notify Access Technology that there was a change in keyboard focus. + ::NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, + static_cast<LONG>(view_index)); +} + +void BrowserToolbarView::WillLoseFocus() { + // Resetting focus state. + acc_focused_view_->SetHotTracked(false); + acc_focused_view_ = NULL; +} + +bool BrowserToolbarView::OnKeyPressed(const ChromeViews::KeyEvent& e) { + // Paranoia check, button should be initialized upon toolbar gaining focus. + if (!acc_focused_view_) + return false; + + int focused_view = GetChildIndex(acc_focused_view_); + int next_view = focused_view; + + switch (e.GetCharacter()) { + case VK_LEFT: + next_view = GetNextAccessibleViewIndex(focused_view, true); + break; + case VK_RIGHT: + next_view = GetNextAccessibleViewIndex(focused_view, false); + break; + case VK_DOWN: + case VK_RETURN: + // VK_SPACE is already handled by the default case. + if (acc_focused_view_->GetID() == VIEW_ID_PAGE_MENU || + acc_focused_view_->GetID() == VIEW_ID_APP_MENU) { + // Safe to cast, given to above check. + static_cast<ChromeViews::MenuButton*>(acc_focused_view_)->Activate(); + // Re-enable hot-tracking, as Activate() will disable it. + acc_focused_view_->SetHotTracked(true); + break; + } + default: + // If key is not handled explicitly, pass it on to view. + return acc_focused_view_->OnKeyPressed(e); + } + + // No buttons enabled or visible. + if (next_view == -1) + return false; + + // Only send an event if focus moved. + if (next_view != focused_view) { + // Remove hot-tracking from old focused button. + acc_focused_view_->SetHotTracked(false); + + // All is well, update the focused child member variable. + acc_focused_view_ = GetChildViewAt(next_view); + + // Hot-track new focused button. + acc_focused_view_->SetHotTracked(true); + + // Retrieve information to generate an MSAA focus event. + int view_id = acc_focused_view_->GetID(); + HWND hwnd = GetViewContainer()->GetHWND(); + + // Notify Access Technology that there was a change in keyboard focus. + ::NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, + static_cast<LONG>(view_id)); + return true; + } + return false; +} + +bool BrowserToolbarView::OnKeyReleased(const ChromeViews::KeyEvent& e) { + // Paranoia check, button should be initialized upon toolbar gaining focus. + if (!acc_focused_view_) + return false; + + // Have keys be handled by the views themselves. + return acc_focused_view_->OnKeyReleased(e); +} + + +void BrowserToolbarView::RunPageMenu(const CPoint& pt, HWND hwnd) { + Menu::AnchorPoint anchor = Menu::TOPRIGHT; + if (UILayoutIsRightToLeft()) + anchor = Menu::TOPLEFT; + + Menu menu(this, anchor, hwnd); + menu.AppendMenuItemWithLabel(IDC_NEWTAB, l10n_util::GetString(IDS_NEWTAB)); + menu.AppendMenuItemWithLabel(IDC_NEWWINDOW, + l10n_util::GetString(IDS_NEWWINDOW)); + menu.AppendMenuItemWithLabel(IDC_GOOFFTHERECORD, + l10n_util::GetString(IDS_GOOFFTHERECORD)); + menu.AppendSeparator(); + // The install menu may be dynamically generated with a contextual label. + // See browser_commands.cc. + menu.AppendMenuItemWithLabel(IDC_CREATE_SHORTCUT, + l10n_util::GetString(IDS_DEFAULT_INSTALL_SITE_LABEL)); + menu.AppendSeparator(); + menu.AppendMenuItemWithLabel(IDC_CUT, l10n_util::GetString(IDS_CUT)); + menu.AppendMenuItemWithLabel(IDC_COPY, l10n_util::GetString(IDS_COPY)); + menu.AppendMenuItemWithLabel(IDC_PASTE, l10n_util::GetString(IDS_PASTE)); + menu.AppendSeparator(); + + menu.AppendMenuItemWithLabel(IDC_FIND, + l10n_util::GetString(IDS_FIND_IN_PAGE)); + menu.AppendMenuItemWithLabel(IDC_SAVEPAGE, + l10n_util::GetString(IDS_SAVEPAGEAS)); + menu.AppendMenuItemWithLabel(IDC_PRINT, l10n_util::GetString(IDS_PRINT)); + menu.AppendSeparator(); + + Menu* zoom_menu = menu.AppendSubMenu(IDC_ZOOM, + l10n_util::GetString(IDS_ZOOM)); + zoom_menu->AppendMenuItemWithLabel(IDC_ZOOM_PLUS, + l10n_util::GetString(IDS_ZOOM_PLUS)); + zoom_menu->AppendMenuItemWithLabel(IDC_ZOOM_NORMAL, + l10n_util::GetString(IDS_ZOOM_NORMAL)); + zoom_menu->AppendMenuItemWithLabel(IDC_ZOOM_MINUS, + l10n_util::GetString(IDS_ZOOM_MINUS)); + + // Create encoding menu. + Menu* encoding_menu = menu.AppendSubMenu(IDC_ENCODING, + l10n_util::GetString(IDS_ENCODING)); + + EncodingMenuControllerDelegate::BuildEncodingMenu(profile_, encoding_menu); + + struct MenuCreateMaterial { + unsigned int menu_id; + unsigned int menu_label_id; + }; + struct MenuCreateMaterial developer_menu_materials[] = { + { IDC_VIEWSOURCE, IDS_VIEWPAGESOURCE }, + { IDC_DEBUGGER, IDS_DEBUGGER }, + { IDC_SHOW_JS_CONSOLE, IDS_VIEWJSCONSOLE }, + { IDC_TASKMANAGER, IDS_TASKMANAGER } + }; + // Append developer menu. + menu.AppendSeparator(); + Menu* developer_menu = + menu.AppendSubMenu(IDC_DEVELOPER, l10n_util::GetString(IDS_DEVELOPER)); + for (int i = 0; i < arraysize(developer_menu_materials); ++i) { + if (developer_menu_materials[i].menu_id) { + developer_menu->AppendMenuItemWithLabel( + developer_menu_materials[i].menu_id, + l10n_util::GetString(developer_menu_materials[i].menu_label_id)); + } else { + developer_menu->AppendSeparator(); + } + } + + menu.AppendSeparator(); + + menu.AppendMenuItemWithLabel(IDS_COMMANDS_REPORTBUG, + l10n_util::GetString(IDS_COMMANDS_REPORTBUG)); + menu.RunMenuAt(pt.x, pt.y); +} + +void BrowserToolbarView::RunAppMenu(const CPoint& pt, HWND hwnd) { + Menu::AnchorPoint anchor = Menu::TOPRIGHT; + if (UILayoutIsRightToLeft()) + anchor = Menu::TOPLEFT; + + Menu menu(this, anchor, hwnd); + menu.AppendMenuItemWithLabel(IDC_SHOW_BOOKMARKS_BAR, + l10n_util::GetString(IDS_SHOW_BOOKMARK_BAR)); + menu.AppendSeparator(); + menu.AppendMenuItemWithLabel(IDC_SHOW_HISTORY, + l10n_util::GetString(IDS_SHOW_HISTORY)); + menu.AppendMenuItemWithLabel(IDC_SHOW_DOWNLOADS, + l10n_util::GetString(IDS_SHOW_DOWNLOADS)); + menu.AppendSeparator(); + menu.AppendMenuItemWithLabel(IDC_CLEAR_BROWSING_DATA, + l10n_util::GetString(IDS_CLEAR_BROWSING_DATA)); + menu.AppendMenuItemWithLabel(IDC_IMPORT_SETTINGS, + l10n_util::GetString(IDS_IMPORT_SETTINGS)); + menu.AppendSeparator(); + menu.AppendMenuItemWithLabel(IDC_OPTIONS, + l10n_util::GetStringF(IDS_OPTIONS, + l10n_util::GetString(IDS_PRODUCT_NAME))); + menu.AppendMenuItemWithLabel(IDC_ABOUT, + l10n_util::GetStringF(IDS_ABOUT, + l10n_util::GetString(IDS_PRODUCT_NAME))); + menu.AppendMenuItemWithLabel(IDC_HELPMENU, l10n_util::GetString(IDS_HELP)); + menu.AppendSeparator(); + menu.AppendMenuItemWithLabel(IDC_EXIT, l10n_util::GetString(IDS_EXIT)); + + menu.RunMenuAt(pt.x, pt.y); +} + +bool BrowserToolbarView::IsItemChecked(int id) const { + if (!profile_) + return false; + if (id == IDC_SHOW_BOOKMARKS_BAR) + return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); + else + return EncodingMenuControllerDelegate::IsItemChecked(id); +} + +void BrowserToolbarView::RunMenu(ChromeViews::View* source, + const CPoint& pt, + HWND hwnd) { + switch (source->GetID()) { + case VIEW_ID_PAGE_MENU: + RunPageMenu(pt, hwnd); + break; + case VIEW_ID_APP_MENU: + RunAppMenu(pt, hwnd); + break; + default: + NOTREACHED() << "Invalid source menu."; + } +} + +bool BrowserToolbarView::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_TOOLBAR; + return true; +} + +bool BrowserToolbarView::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + (*name).assign(accessible_name_); + return true; + } + return false; +} + +void BrowserToolbarView::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +int BrowserToolbarView::GetNextAccessibleViewIndex(int view_index, + bool nav_left) { + int modifier = 1; + + if (nav_left) + modifier = -1; + + int current_view_index = view_index + modifier; + + if ((current_view_index >= 0) && (current_view_index < GetChildViewCount())) { + // Skip the location bar, as it has its own keyboard navigation. + if (current_view_index == GetChildIndex(location_bar_)) + current_view_index += modifier; + + view_index = current_view_index; + } + return view_index; +} + +int BrowserToolbarView::GetDragOperations(ChromeViews::View* sender, + int x, + int y) { + DCHECK(sender == star_); + if (model_->input_in_progress() || !tab_ || !tab_->ShouldDisplayURL() || + !tab_->GetURL().is_valid()) { + return DragDropTypes::DRAG_NONE; + } + return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK; +} + +void BrowserToolbarView::WriteDragData(ChromeViews::View* sender, + int press_x, + int press_y, + OSExchangeData* data) { + DCHECK( + GetDragOperations(sender, press_x, press_y) != DragDropTypes::DRAG_NONE); + + UserMetrics::RecordAction(L"Toolbar_DragStar", profile_); + + drag_utils::SetURLAndDragImage(tab_->GetURL(), tab_->GetTitle(), + tab_->GetFavIcon(), data); +} + +TabContents* BrowserToolbarView::GetTabContents() { + return tab_; +} + +void BrowserToolbarView::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NOTIFY_PREF_CHANGED) { + std::wstring* pref_name = Details<std::wstring>(details).ptr(); + if (*pref_name == prefs::kShowHomeButton) { + Layout(); + SchedulePaint(); + } + } +} + +bool BrowserToolbarView::GetAcceleratorInfo(int id, + ChromeViews::Accelerator* accel) { + // The standard Ctrl-X, Ctrl-V and Ctrl-C are not defined as accelerators + // anywhere so we need to check for them explicitly here. + // TODO(cpu) Bug 1109102. Query WebKit land for the actual bindings. + switch (id) { + case IDC_CUT: + *accel = ChromeViews::Accelerator(L'X', false, true, false); + return true; + case IDC_COPY: + *accel = ChromeViews::Accelerator(L'C', false, true, false); + return true; + case IDC_PASTE: + *accel = ChromeViews::Accelerator(L'V', false, true, false); + return true; + } + // Else, we retrieve the accelerator information from the frame. + return GetViewContainer()->GetAccelerator(id, accel); +} diff --git a/chrome/browser/views/toolbar_view.h b/chrome/browser/views/toolbar_view.h new file mode 100644 index 0000000..29d9f19 --- /dev/null +++ b/chrome/browser/views/toolbar_view.h @@ -0,0 +1,197 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_VIEWS_TOOLBAR_VIEW_H__ +#define CHROME_BROWSER_VIEWS_TOOLBAR_VIEW_H__ + +#include <vector> + +#include "base/scoped_ptr.h" +#include "chrome/browser/back_forward_menu_model.h" +#include "chrome/browser/controller.h" +#include "chrome/browser/encoding_menu_controller_delegate.h" +#include "chrome/browser/views/dom_view.h" +#include "chrome/browser/views/go_button.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/common/pref_member.h" +#include "chrome/views/menu.h" +#include "chrome/views/menu_button.h" +#include "chrome/views/view.h" +#include "chrome/views/view_menu_delegate.h" + +class Browser; +class Profile; +class ToolbarStarToggle; + +/////////////////////////////////////////////////////////////////////////////// +// +// BrowserToolbarView class +// +// The BrowserToolbarView is responsible for constructing the content of and +// rendering the Toolbar used in the Browser Window +// +/////////////////////////////////////////////////////////////////////////////// +class BrowserToolbarView : public ChromeViews::View, + public EncodingMenuControllerDelegate, + public ChromeViews::ViewMenuDelegate, + public ChromeViews::DragController, + public LocationBarView::Delegate, + public NotificationObserver { + public: + BrowserToolbarView(CommandController* controller, Browser* browser); + virtual ~BrowserToolbarView(); + + // Create the contents of the Browser Toolbar + void Init(Profile* profile); + + // ChromeViews::View + virtual void Layout(); + virtual void DidGainFocus(); + virtual void WillLoseFocus(); + virtual bool OnKeyPressed(const ChromeViews::KeyEvent& e); + virtual bool OnKeyReleased(const ChromeViews::KeyEvent& e); + + // Overridden from EncodingMenuControllerDelegate: + virtual bool IsItemChecked(int id) const; + + // Overridden from Menu::BaseControllerDelegate: + virtual bool GetAcceleratorInfo(int id, ChromeViews::Accelerator* accel); + + // ChromeViews::MenuDelegate + virtual void RunMenu(ChromeViews::View* source, const CPoint& pt, HWND hwnd); + + // Sets the profile which is active on the currently-active tab. + void SetProfile(Profile* profile); + Profile* profile() { return profile_; } + + ToolbarStarToggle* star_button() { return star_; } + + GoButton* GetGoButton() { return go_; } + + LocationBarView* GetLocationBarView() const { return location_bar_; } + + // Updates the toolbar (and transitively the location bar) with the states of + // the specified |tab|. If |should_restore_state| is true, we're switching + // (back?) to this tab and should restore any previous location bar state + // (such as user editing) as well. + void Update(TabContents* tab, bool should_restore_state); + + void OnInputInProgress(bool in_progress); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Assigns an accessible string name. + void SetAccessibleName(const std::wstring& name); + + // Returns the index of the next view of the toolbar, starting from the given + // view index (skipping the location bar), in the given navigation direction + // (nav_left true means navigation right to left, and vice versa). -1 finds + // first accessible child, based on the above policy. + int GetNextAccessibleViewIndex(int view_index, bool nav_left); + + ChromeViews::View* GetAccFocusedChildView() { + return acc_focused_view_; + } + + // Returns the selected tab. + virtual TabContents* GetTabContents(); + + Browser* browser() { return browser_; } + + private: + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // DragController methods for the star button. These allow the drag if the + // user hasn't edited the text, the url is valid and should be displayed. + virtual void WriteDragData(View* sender, + int press_x, + int press_y, + OSExchangeData* data); + virtual int GetDragOperations(View* sender, int x, int y); + + // Set up the various Views in the toolbar + void CreateLeftSideControls(); + void CreateCenterStack(Profile* profile); + void CreateRightSideControls(Profile* profile); + + // Updates the controls to display the security appropriately (highlighted if + // secure). + void SetSecurityLevel(ToolbarModel::SecurityLevel security_level); + + // Show the page menu. + void RunPageMenu(const CPoint& pt, HWND hwnd); + + // Show the app menu. + void RunAppMenu(const CPoint& pt, HWND hwnd); + + // This View's Command Controller + CommandController* controller_; + + scoped_ptr<BackForwardMenuModel> back_menu_model_; + scoped_ptr<BackForwardMenuModel> forward_menu_model_; + + // The model that contains the security level, text, icon to display... + ToolbarModel* model_; + + // Storage of strings needed for accessibility. + std::wstring accessible_name_; + // Child view currently having MSAA focus (location bar excluded from arrow + // navigation). + ChromeViews::View* acc_focused_view_; + + // Controls + ChromeViews::Button* back_; + ChromeViews::Button* forward_; + ChromeViews::Button* reload_; + ChromeViews::Button* home_; + ToolbarStarToggle* star_; + LocationBarView* location_bar_; + GoButton* go_; + ChromeViews::MenuButton* page_menu_; + ChromeViews::MenuButton* app_menu_; + Profile* profile_; + Browser* browser_; + + // Current tab we're showing state for. + TabContents* tab_; + + // Controls whether or not a home button should be shown on the toolbar. + BooleanPrefMember show_home_button_; +}; + +#endif // CHROME_BROWSER_VIEWS_TOOLBAR_VIEW_H__ |