1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/reload_button.h" 6 7 #include "base/strings/utf_string_conversions.h" 8 #include "chrome/app/chrome_command_ids.h" 9 #include "chrome/browser/command_updater.h" 10 #include "chrome/browser/search/search.h" 11 #include "chrome/browser/ui/search/search_model.h" 12 #include "chrome/browser/ui/views/location_bar/location_bar_view.h" 13 #include "grit/generated_resources.h" 14 #include "grit/theme_resources.h" 15 #include "ui/base/l10n/l10n_util.h" 16 #include "ui/base/models/simple_menu_model.h" 17 #include "ui/base/theme_provider.h" 18 #include "ui/base/window_open_disposition.h" 19 #include "ui/views/metrics.h" 20 #include "ui/views/widget/widget.h" 21 22 23 namespace { 24 25 const int kReloadImages[] = 26 { IDR_RELOAD, IDR_RELOAD_H, IDR_RELOAD_P, IDR_RELOAD_D }; 27 28 const int kStopImages[] = { IDR_STOP, IDR_STOP_H, IDR_STOP_P, IDR_STOP_D }; 29 30 // Contents of the Reload drop-down menu. 31 const int kReloadMenuItems[] = { 32 IDS_RELOAD_MENU_NORMAL_RELOAD_ITEM, 33 IDS_RELOAD_MENU_HARD_RELOAD_ITEM, 34 IDS_RELOAD_MENU_EMPTY_AND_HARD_RELOAD_ITEM, 35 }; 36 37 } // namespace 38 39 40 // ReloadButton --------------------------------------------------------------- 41 42 // static 43 const char ReloadButton::kViewClassName[] = "ReloadButton"; 44 45 ReloadButton::ReloadButton(LocationBarView* location_bar, 46 CommandUpdater* command_updater) 47 : ButtonDropDown(this, CreateMenuModel()), 48 location_bar_(location_bar), 49 command_updater_(command_updater), 50 intended_mode_(MODE_RELOAD), 51 visible_mode_(MODE_RELOAD), 52 double_click_timer_delay_( 53 base::TimeDelta::FromMilliseconds(views::GetDoubleClickInterval())), 54 stop_to_reload_timer_delay_(base::TimeDelta::FromMilliseconds(1350)), 55 menu_enabled_(false), 56 testing_mouse_hovered_(false), 57 testing_reload_count_(0) { 58 } 59 60 ReloadButton::~ReloadButton() { 61 } 62 63 void ReloadButton::ChangeMode(Mode mode, bool force) { 64 intended_mode_ = mode; 65 66 // If the change is forced, or the user isn't hovering the icon, or it's safe 67 // to change it to the other image type, make the change immediately; 68 // otherwise we'll let it happen later. 69 if (force || (!IsMouseHovered() && !testing_mouse_hovered_) || 70 ((mode == MODE_STOP) ? 71 !double_click_timer_.IsRunning() : (visible_mode_ != MODE_STOP))) { 72 double_click_timer_.Stop(); 73 stop_to_reload_timer_.Stop(); 74 ChangeModeInternal(mode); 75 SetEnabled(true); 76 77 // We want to disable the button if we're preventing a change from stop to 78 // reload due to hovering, but not if we're preventing a change from reload to 79 // stop due to the double-click timer running. (Disabled reload state is only 80 // applicable when instant extended API is enabled and mode is NTP, which is 81 // handled just above.) 82 } else if (visible_mode_ != MODE_RELOAD) { 83 SetEnabled(false); 84 85 // Go ahead and change to reload after a bit, which allows repeated reloads 86 // without moving the mouse. 87 if (!stop_to_reload_timer_.IsRunning()) { 88 stop_to_reload_timer_.Start(FROM_HERE, stop_to_reload_timer_delay_, this, 89 &ReloadButton::OnStopToReloadTimer); 90 } 91 } 92 } 93 94 void ReloadButton::LoadImages(ui::ThemeProvider* tp) { 95 DCHECK_EQ(static_cast<int>(arraysize(kReloadImages)), STATE_COUNT); 96 DCHECK_EQ(static_cast<int>(arraysize(kStopImages)), STATE_COUNT); 97 98 gfx::ImageSkia* reload_images = images_; 99 gfx::ImageSkia* stop_images = alternate_images_; 100 if (visible_mode_ == MODE_STOP) 101 std::swap(reload_images, stop_images); 102 103 for (int i = 0; i < STATE_COUNT; i++) { 104 reload_images[i] = *(tp->GetImageSkiaNamed(kReloadImages[i])); 105 stop_images[i] = *(tp->GetImageSkiaNamed(kStopImages[i])); 106 } 107 108 SchedulePaint(); 109 PreferredSizeChanged(); 110 } 111 112 void ReloadButton::OnMouseExited(const ui::MouseEvent& event) { 113 ButtonDropDown::OnMouseExited(event); 114 if (!IsMenuShowing()) 115 ChangeMode(intended_mode_, true); 116 } 117 118 bool ReloadButton::GetTooltipText(const gfx::Point& p, 119 string16* tooltip) const { 120 int reload_tooltip = menu_enabled_ ? 121 IDS_TOOLTIP_RELOAD_WITH_MENU : IDS_TOOLTIP_RELOAD; 122 int text_id = (visible_mode_ == MODE_RELOAD) ? 123 reload_tooltip : IDS_TOOLTIP_STOP; 124 tooltip->assign(l10n_util::GetStringUTF16(text_id)); 125 return true; 126 } 127 128 const char* ReloadButton::GetClassName() const { 129 return kViewClassName; 130 } 131 132 void ReloadButton::GetAccessibleState(ui::AccessibleViewState* state) { 133 if (menu_enabled_) 134 ButtonDropDown::GetAccessibleState(state); 135 else 136 CustomButton::GetAccessibleState(state); 137 } 138 139 bool ReloadButton::ShouldShowMenu() { 140 return menu_enabled_ && (visible_mode_ == MODE_RELOAD); 141 } 142 143 void ReloadButton::ShowDropDownMenu(ui::MenuSourceType source_type) { 144 ButtonDropDown::ShowDropDownMenu(source_type); // Blocks. 145 ChangeMode(intended_mode_, true); 146 } 147 148 void ReloadButton::ButtonPressed(views::Button* /* button */, 149 const ui::Event& event) { 150 ClearPendingMenu(); 151 152 if (visible_mode_ == MODE_STOP) { 153 if (command_updater_) 154 command_updater_->ExecuteCommandWithDisposition(IDC_STOP, CURRENT_TAB); 155 // The user has clicked, so we can feel free to update the button, 156 // even if the mouse is still hovering. 157 ChangeMode(MODE_RELOAD, true); 158 } else if (!double_click_timer_.IsRunning()) { 159 // Shift-clicking or ctrl-clicking the reload button means we should ignore 160 // any cached content. 161 int command; 162 int flags = event.flags(); 163 if (event.IsShiftDown() || event.IsControlDown()) { 164 command = IDC_RELOAD_IGNORING_CACHE; 165 // Mask off Shift and Control so they don't affect the disposition below. 166 flags &= ~(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN); 167 } else { 168 command = IDC_RELOAD; 169 } 170 171 // Start a timer - while this timer is running, the reload button cannot be 172 // changed to a stop button. We do not set |intended_mode_| to MODE_STOP 173 // here as the browser will do that when it actually starts loading (which 174 // may happen synchronously, thus the need to do this before telling the 175 // browser to execute the reload command). 176 double_click_timer_.Start(FROM_HERE, double_click_timer_delay_, this, 177 &ReloadButton::OnDoubleClickTimer); 178 179 ExecuteBrowserCommand(command, flags); 180 ++testing_reload_count_; 181 } 182 } 183 184 bool ReloadButton::IsCommandIdChecked(int command_id) const { 185 return false; 186 } 187 188 bool ReloadButton::IsCommandIdEnabled(int command_id) const { 189 return true; 190 } 191 192 bool ReloadButton::IsCommandIdVisible(int command_id) const { 193 return true; 194 } 195 196 bool ReloadButton::GetAcceleratorForCommandId(int command_id, 197 ui::Accelerator* accelerator) { 198 switch (command_id) { 199 case IDS_RELOAD_MENU_NORMAL_RELOAD_ITEM: 200 GetWidget()->GetAccelerator(IDC_RELOAD, accelerator); 201 return true; 202 case IDS_RELOAD_MENU_HARD_RELOAD_ITEM: 203 GetWidget()->GetAccelerator(IDC_RELOAD_IGNORING_CACHE, accelerator); 204 return true; 205 } 206 return GetWidget()->GetAccelerator(command_id, accelerator); 207 } 208 209 void ReloadButton::ExecuteCommand(int command_id, int event_flags) { 210 int browser_command = 0; 211 switch (command_id) { 212 case IDS_RELOAD_MENU_NORMAL_RELOAD_ITEM: 213 browser_command = IDC_RELOAD; 214 break; 215 case IDS_RELOAD_MENU_HARD_RELOAD_ITEM: 216 browser_command = IDC_RELOAD_IGNORING_CACHE; 217 break; 218 case IDS_RELOAD_MENU_EMPTY_AND_HARD_RELOAD_ITEM: 219 browser_command = IDC_RELOAD_CLEARING_CACHE; 220 break; 221 default: 222 NOTREACHED(); 223 } 224 ExecuteBrowserCommand(browser_command, event_flags); 225 } 226 227 ui::SimpleMenuModel* ReloadButton::CreateMenuModel() { 228 ui::SimpleMenuModel* menu_model = new ui::SimpleMenuModel(this); 229 for (size_t i = 0; i < arraysize(kReloadMenuItems); ++i) 230 menu_model->AddItemWithStringId(kReloadMenuItems[i], kReloadMenuItems[i]); 231 232 return menu_model; 233 } 234 235 void ReloadButton::ExecuteBrowserCommand(int command, int event_flags) { 236 if (!command_updater_) 237 return; 238 239 WindowOpenDisposition disposition = 240 ui::DispositionFromEventFlags(event_flags); 241 if ((disposition == CURRENT_TAB) && location_bar_) { 242 // Forcibly reset the location bar, since otherwise it won't discard any 243 // ongoing user edits, since it doesn't realize this is a user-initiated 244 // action. 245 location_bar_->Revert(); 246 } 247 command_updater_->ExecuteCommandWithDisposition(command, disposition); 248 } 249 250 void ReloadButton::ChangeModeInternal(Mode mode) { 251 if (visible_mode_ == mode) 252 return; 253 254 for (size_t i = 0; i < STATE_COUNT; ++i) 255 std::swap(images_[i], alternate_images_[i]); 256 visible_mode_ = mode; 257 SchedulePaint(); 258 } 259 260 void ReloadButton::OnDoubleClickTimer() { 261 if (!IsMenuShowing()) 262 ChangeMode(intended_mode_, false); 263 } 264 265 void ReloadButton::OnStopToReloadTimer() { 266 DCHECK(!IsMenuShowing()); 267 ChangeMode(intended_mode_, true); 268 } 269