// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/gtk/reload_button_gtk.h" #include "app/l10n_util.h" #include "base/logging.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/browser.h" #include "chrome/browser/gtk/gtk_chrome_button.h" #include "chrome/browser/gtk/gtk_theme_provider.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/browser/gtk/location_bar_view_gtk.h" #include "chrome/common/notification_service.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" // The width of this button in GTK+ theme mode. The Stop and Refresh stock icons // can be different sizes; this variable is used to make sure that the button // doesn't change sizes when switching between the two. static int GtkButtonWidth = 0; ReloadButtonGtk::ReloadButtonGtk(LocationBarViewGtk* location_bar, Browser* browser) : location_bar_(location_bar), browser_(browser), button_delay_(0), pretend_timer_is_running_for_unittest_(false), intended_mode_(MODE_RELOAD), visible_mode_(MODE_RELOAD), theme_provider_(browser ? GtkThemeProvider::GetFrom(browser->profile()) : NULL), reload_(theme_provider_, IDR_RELOAD, IDR_RELOAD_P, IDR_RELOAD_H, 0), stop_(theme_provider_, IDR_STOP, IDR_STOP_P, IDR_STOP_H, IDR_STOP_D), widget_(gtk_chrome_button_new()) { gtk_widget_set_size_request(widget(), reload_.Width(), reload_.Height()); gtk_widget_set_app_paintable(widget(), TRUE); g_signal_connect(widget(), "clicked", G_CALLBACK(OnClickedThunk), this); g_signal_connect(widget(), "expose-event", G_CALLBACK(OnExposeThunk), this); g_signal_connect(widget(), "leave-notify-event", G_CALLBACK(OnLeaveNotifyThunk), this); GTK_WIDGET_UNSET_FLAGS(widget(), GTK_CAN_FOCUS); gtk_widget_set_has_tooltip(widget(), TRUE); g_signal_connect(widget(), "query-tooltip", G_CALLBACK(OnQueryTooltipThunk), this); hover_controller_.Init(widget()); gtk_util::SetButtonTriggersNavigation(widget()); if (theme_provider_) { theme_provider_->InitThemesFor(this); registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, Source(theme_provider_)); } } ReloadButtonGtk::~ReloadButtonGtk() { widget_.Destroy(); } void ReloadButtonGtk::ChangeMode(Mode mode, bool force) { intended_mode_ = mode; // If the change is forced, or the user isn't hovering the icon, or it's safe // to change it to the other image type, make the change immediately; // otherwise we'll let it happen later. if (force || GTK_WIDGET_STATE(widget()) == GTK_STATE_NORMAL || ((mode == MODE_STOP) ? !timer_running() : (visible_mode_ != MODE_STOP))) { timer_.Stop(); visible_mode_ = mode; stop_.set_paint_override(-1); gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(widget_.get())); UpdateThemeButtons(); gtk_widget_queue_draw(widget()); } else if (visible_mode_ != MODE_RELOAD) { // If you read the views implementation of reload_button.cc, you'll see // that instead of screwing with paint states, the views implementation // just changes whether the view is enabled. We can't do that here because // changing the widget state to GTK_STATE_INSENSITIVE will cause a cascade // of messages on all its children and will also trigger a synthesized // leave notification and prevent the real leave notification from turning // the button back to normal. So instead, override the stop_ paint state // for chrome-theme mode, and use this as a flag to discard click events. stop_.set_paint_override(GTK_STATE_INSENSITIVE); // Also set the gtk_chrome_button paint state to insensitive to hide // the border drawn around the stop icon. gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget_.get()), GTK_STATE_INSENSITIVE); // If we're in GTK theme mode, we need to also render the correct icon for // the stop/insensitive since we won't be using |stop_| to render the icon. UpdateThemeButtons(); } } void ReloadButtonGtk::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(NotificationType::BROWSER_THEME_CHANGED == type); GtkThemeProvider* provider = static_cast( Source(source).ptr()); DCHECK_EQ(provider, theme_provider_); GtkButtonWidth = 0; UpdateThemeButtons(); } void ReloadButtonGtk::OnButtonTimer() { ChangeMode(intended_mode_, false); } void ReloadButtonGtk::OnClicked(GtkWidget* sender) { if (visible_mode_ == MODE_STOP) { // The stop button is disabled because the user hovered over the button // until the stop action is no longer selectable. if (stop_.paint_override() == GTK_STATE_INSENSITIVE) return; if (browser_) browser_->Stop(); // The user has clicked, so we can feel free to update the button, // even if the mouse is still hovering. ChangeMode(MODE_RELOAD, true); } else if (!timer_running()) { // Shift-clicking or Ctrl-clicking the reload button means we should ignore // any cached content. int command; GdkModifierType modifier_state; gtk_get_current_event_state(&modifier_state); guint modifier_state_uint = modifier_state; if (modifier_state_uint & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { command = IDC_RELOAD_IGNORING_CACHE; // Mask off Shift and Control so they don't affect the disposition below. modifier_state_uint &= ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK); } else { command = IDC_RELOAD; } WindowOpenDisposition disposition = event_utils::DispositionFromEventFlags(modifier_state_uint); if (disposition == CURRENT_TAB) { // Forcibly reset the location bar, since otherwise it won't discard any // ongoing user edits, since it doesn't realize this is a user-initiated // action. location_bar_->Revert(); } // Figure out the system double-click time. if (button_delay_ == 0) { GtkSettings* settings = gtk_settings_get_default(); g_object_get(G_OBJECT(settings), "gtk-double-click-time", &button_delay_, NULL); } // Start a timer - while this timer is running, the reload button cannot be // changed to a stop button. We do not set |intended_mode_| to MODE_STOP // here as the browser will do that when it actually starts loading (which // may happen synchronously, thus the need to do this before telling the // browser to execute the reload command). timer_.Stop(); timer_.Start(base::TimeDelta::FromMilliseconds(button_delay_), this, &ReloadButtonGtk::OnButtonTimer); if (browser_) browser_->ExecuteCommandWithDisposition(command, disposition); } } gboolean ReloadButtonGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e) { if (theme_provider_ && theme_provider_->UseGtkTheme()) return FALSE; return ((visible_mode_ == MODE_RELOAD) ? reload_ : stop_).OnExpose( widget, e, hover_controller_.GetCurrentValue()); } gboolean ReloadButtonGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) { ChangeMode(intended_mode_, true); return FALSE; } gboolean ReloadButtonGtk::OnQueryTooltip(GtkWidget* sender, gint x, gint y, gboolean keyboard_mode, GtkTooltip* tooltip) { // |location_bar_| can be NULL in tests. if (!location_bar_) return FALSE; gtk_tooltip_set_text(tooltip, l10n_util::GetStringUTF8( (visible_mode_ == MODE_RELOAD) ? IDS_TOOLTIP_RELOAD : IDS_TOOLTIP_STOP).c_str()); return TRUE; } void ReloadButtonGtk::UpdateThemeButtons() { bool use_gtk = theme_provider_ && theme_provider_->UseGtkTheme(); if (use_gtk) { gtk_widget_ensure_style(widget()); GtkIconSet* icon_set = gtk_style_lookup_icon_set( widget()->style, (visible_mode_ == MODE_RELOAD) ? GTK_STOCK_REFRESH : GTK_STOCK_STOP); if (icon_set) { GtkStateType state = static_cast( GTK_WIDGET_STATE(widget())); if (visible_mode_ == MODE_STOP && stop_.paint_override() != -1) state = static_cast(stop_.paint_override()); GdkPixbuf* pixbuf = gtk_icon_set_render_icon( icon_set, widget()->style, gtk_widget_get_direction(widget()), state, GTK_ICON_SIZE_SMALL_TOOLBAR, widget(), NULL); gtk_button_set_image(GTK_BUTTON(widget()), gtk_image_new_from_pixbuf(pixbuf)); g_object_unref(pixbuf); } gtk_widget_set_size_request(widget(), -1, -1); GtkRequisition req; gtk_widget_size_request(widget(), &req); GtkButtonWidth = std::max(GtkButtonWidth, req.width); gtk_widget_set_size_request(widget(), GtkButtonWidth, -1); gtk_widget_set_app_paintable(widget(), FALSE); gtk_widget_set_double_buffered(widget(), TRUE); } else { gtk_button_set_image(GTK_BUTTON(widget()), NULL); gtk_widget_set_size_request(widget(), reload_.Width(), reload_.Height()); gtk_widget_set_app_paintable(widget(), TRUE); // We effectively double-buffer by virtue of having only one image... gtk_widget_set_double_buffered(widget(), FALSE); } gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(widget()), use_gtk); }