| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/protector/settings_change_global_error.h" |
| |
| #include <bitset> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/protector/base_setting_change.h" |
| #include "chrome/browser/protector/settings_change_global_error_delegate.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/global_error/global_error_service.h" |
| #include "chrome/browser/ui/global_error/global_error_service_factory.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| using content::BrowserThread; |
| |
| namespace protector { |
| |
| namespace { |
| |
| // Timeout before the global error is removed (wrench menu item disappears). |
| const int kMenuItemDisplayPeriodMs = 10*60*1000; // 10 min |
| |
| // Unset bits indicate available command IDs. |
| static base::LazyInstance< |
| std::bitset<IDC_SHOW_SETTINGS_CHANGE_LAST - |
| IDC_SHOW_SETTINGS_CHANGE_FIRST + 1> > menu_ids = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| SettingsChangeGlobalError::SettingsChangeGlobalError( |
| BaseSettingChange* change, |
| SettingsChangeGlobalErrorDelegate* delegate) |
| : change_(change), |
| delegate_(delegate), |
| profile_(NULL), |
| closed_by_button_(false), |
| show_on_browser_activation_(false), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
| menu_id_(-1) { |
| DCHECK(delegate_); |
| for (int i = IDC_SHOW_SETTINGS_CHANGE_FIRST; |
| i <= IDC_SHOW_SETTINGS_CHANGE_LAST; i++) { |
| if (!menu_ids.Get()[i - IDC_SHOW_SETTINGS_CHANGE_FIRST]) { |
| menu_id_ = i; |
| menu_ids.Get().set(i - IDC_SHOW_SETTINGS_CHANGE_FIRST); |
| break; |
| } |
| } |
| DCHECK(menu_id_ >= 0) << "Out of command IDs for SettingsChangeGlobalError"; |
| } |
| |
| SettingsChangeGlobalError::~SettingsChangeGlobalError() { |
| if (profile_) |
| RemoveFromProfile(); |
| if (menu_id_ >= 0) |
| menu_ids.Get().reset(menu_id_ - IDC_SHOW_SETTINGS_CHANGE_FIRST); |
| } |
| |
| void SettingsChangeGlobalError::AddToProfile(Profile* profile, |
| bool show_bubble) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| profile_ = profile; |
| GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError(this); |
| BrowserList::AddObserver(this); |
| if (show_bubble) { |
| ShowBubble(); |
| } else { |
| // Start inactivity timer. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&SettingsChangeGlobalError::OnInactiveTimeout, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kMenuItemDisplayPeriodMs)); |
| } |
| } |
| |
| void SettingsChangeGlobalError::RemoveFromProfile() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (profile_) { |
| GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(this); |
| profile_ = NULL; |
| } |
| BrowserList::RemoveObserver(this); |
| // This will delete |this|. |
| delegate_->OnRemovedFromProfile(this); |
| } |
| |
| void SettingsChangeGlobalError::ShowBubble() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| Browser* browser = browser::FindTabbedBrowser( |
| profile_, |
| // match incognito |
| true); |
| if (browser) |
| ShowBubbleInBrowser(browser); |
| } |
| |
| bool SettingsChangeGlobalError::HasBadge() { |
| return true; |
| } |
| |
| int SettingsChangeGlobalError::GetBadgeResourceID() { |
| return change_->GetBadgeIconID(); |
| } |
| |
| bool SettingsChangeGlobalError::HasMenuItem() { |
| return true; |
| } |
| |
| int SettingsChangeGlobalError::MenuItemCommandID() { |
| return menu_id_; |
| } |
| |
| string16 SettingsChangeGlobalError::MenuItemLabel() { |
| return change_->GetBubbleTitle(); |
| } |
| |
| int SettingsChangeGlobalError::MenuItemIconResourceID() { |
| return change_->GetMenuItemIconID(); |
| } |
| |
| void SettingsChangeGlobalError::ExecuteMenuItem(Browser* browser) { |
| ShowBubbleInBrowser(browser); |
| } |
| |
| bool SettingsChangeGlobalError::HasBubbleView() { |
| return true; |
| } |
| |
| int SettingsChangeGlobalError::GetBubbleViewIconResourceID() { |
| return change_->GetBubbleIconID(); |
| } |
| |
| string16 SettingsChangeGlobalError::GetBubbleViewTitle() { |
| return change_->GetBubbleTitle(); |
| } |
| |
| string16 SettingsChangeGlobalError::GetBubbleViewMessage() { |
| return change_->GetBubbleMessage(); |
| } |
| |
| // The Accept and Revert buttons are swapped like the 'server' and 'client' |
| // concepts in X11. Accept button (the default one) discards changes |
| // (keeps using previous setting) while cancel button applies changes |
| // (switches to the new setting). This is sick and blows my mind. - ivankr |
| |
| string16 SettingsChangeGlobalError::GetBubbleViewAcceptButtonLabel() { |
| return change_->GetDiscardButtonText(); |
| } |
| |
| string16 SettingsChangeGlobalError::GetBubbleViewCancelButtonLabel() { |
| return change_->GetApplyButtonText(); |
| } |
| |
| void SettingsChangeGlobalError::OnBubbleViewDidClose(Browser* browser) { |
| // The bubble may be closed as the result of RemoveFromProfile call when |
| // merging this error with another one. |
| if (!profile_) |
| return; |
| if (!closed_by_button_) { |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&SettingsChangeGlobalError::OnInactiveTimeout, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kMenuItemDisplayPeriodMs)); |
| #if !defined(TOOLKIT_GTK) |
| // TODO(ivankr): the logic for redisplaying bubble is disabled on Gtk, see |
| // http://crbug.com/115719. |
| if (browser->window() && |
| !platform_util::IsWindowActive(browser->window()->GetNativeWindow())) { |
| // Bubble closed because the entire window lost activation, display |
| // again when a window gets active. |
| show_on_browser_activation_ = true; |
| } |
| #endif |
| } else { |
| RemoveFromProfile(); |
| } |
| } |
| |
| void SettingsChangeGlobalError::BubbleViewAcceptButtonPressed( |
| Browser* browser) { |
| closed_by_button_ = true; |
| delegate_->OnDiscardChange(this, browser); |
| } |
| |
| void SettingsChangeGlobalError::BubbleViewCancelButtonPressed( |
| Browser* browser) { |
| closed_by_button_ = true; |
| delegate_->OnApplyChange(this, browser); |
| } |
| |
| void SettingsChangeGlobalError::OnBrowserSetLastActive( |
| Browser* browser) { |
| if (show_on_browser_activation_ && browser && browser->is_type_tabbed()) { |
| // A tabbed browser window got activated, show the error bubble again. |
| // Calling ShowBubble() immediately from here does not always work because |
| // the old browser window may still have focus. |
| // Multiple posted ShowBubble() calls are fine since the first successful |
| // one will invalidate all the weak pointers. |
| // Note that ShowBubble() will display the bubble in the last active browser |
| // (which may not be |browser| at the moment ShowBubble() is executed). |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&SettingsChangeGlobalError::ShowBubble, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void SettingsChangeGlobalError::ShowBubbleInBrowser(Browser* browser) { |
| show_on_browser_activation_ = false; |
| // Cancel any previously posted tasks so that the global error |
| // does not get removed on timeout while still showing the bubble. |
| weak_factory_.InvalidateWeakPtrs(); |
| ShowBubbleView(browser); |
| } |
| |
| void SettingsChangeGlobalError::OnInactiveTimeout() { |
| delegate_->OnDecisionTimeout(this); |
| RemoveFromProfile(); |
| } |
| |
| } // namespace protector |