Home | History | Annotate | Download | only in gtk
      1 // Copyright (c) 2011 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/gtk/reload_button_gtk.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "chrome/app/chrome_command_ids.h"
     11 #include "chrome/browser/ui/browser.h"
     12 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
     13 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     14 #include "chrome/browser/ui/gtk/gtk_util.h"
     15 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
     16 #include "content/common/notification_source.h"
     17 #include "grit/generated_resources.h"
     18 #include "grit/theme_resources.h"
     19 #include "ui/base/l10n/l10n_util.h"
     20 
     21 // The width of this button in GTK+ theme mode. The Stop and Refresh stock icons
     22 // can be different sizes; this variable is used to make sure that the button
     23 // doesn't change sizes when switching between the two.
     24 static int GtkButtonWidth = 0;
     25 
     26 ////////////////////////////////////////////////////////////////////////////////
     27 // ReloadButton, public:
     28 
     29 ReloadButtonGtk::ReloadButtonGtk(LocationBarViewGtk* location_bar,
     30                                  Browser* browser)
     31     : location_bar_(location_bar),
     32       browser_(browser),
     33       intended_mode_(MODE_RELOAD),
     34       visible_mode_(MODE_RELOAD),
     35       theme_service_(browser ?
     36                      GtkThemeService::GetFrom(browser->profile()) : NULL),
     37       reload_(theme_service_, IDR_RELOAD, IDR_RELOAD_P, IDR_RELOAD_H, 0),
     38       stop_(theme_service_, IDR_STOP, IDR_STOP_P, IDR_STOP_H, IDR_STOP_D),
     39       widget_(gtk_chrome_button_new()),
     40       stop_to_reload_timer_delay_(base::TimeDelta::FromMilliseconds(1350)),
     41       testing_mouse_hovered_(false),
     42       testing_reload_count_(0) {
     43   gtk_widget_set_size_request(widget(), reload_.Width(), reload_.Height());
     44 
     45   gtk_widget_set_app_paintable(widget(), TRUE);
     46 
     47   g_signal_connect(widget(), "clicked", G_CALLBACK(OnClickedThunk), this);
     48   g_signal_connect(widget(), "expose-event", G_CALLBACK(OnExposeThunk), this);
     49   g_signal_connect(widget(), "leave-notify-event",
     50                    G_CALLBACK(OnLeaveNotifyThunk), this);
     51   GTK_WIDGET_UNSET_FLAGS(widget(), GTK_CAN_FOCUS);
     52 
     53   gtk_widget_set_has_tooltip(widget(), TRUE);
     54   g_signal_connect(widget(), "query-tooltip", G_CALLBACK(OnQueryTooltipThunk),
     55                    this);
     56 
     57   hover_controller_.Init(widget());
     58   gtk_util::SetButtonTriggersNavigation(widget());
     59 
     60   if (theme_service_) {
     61     theme_service_->InitThemesFor(this);
     62     registrar_.Add(this,
     63                    NotificationType::BROWSER_THEME_CHANGED,
     64                    Source<ThemeService>(theme_service_));
     65   }
     66 
     67   // Set the default double-click timer delay to the system double-click time.
     68   int timer_delay_ms;
     69   GtkSettings* settings = gtk_settings_get_default();
     70   g_object_get(G_OBJECT(settings), "gtk-double-click-time", &timer_delay_ms,
     71                NULL);
     72   double_click_timer_delay_ = base::TimeDelta::FromMilliseconds(timer_delay_ms);
     73 }
     74 
     75 ReloadButtonGtk::~ReloadButtonGtk() {
     76   widget_.Destroy();
     77 }
     78 
     79 void ReloadButtonGtk::ChangeMode(Mode mode, bool force) {
     80   intended_mode_ = mode;
     81 
     82   // If the change is forced, or the user isn't hovering the icon, or it's safe
     83   // to change it to the other image type, make the change immediately;
     84   // otherwise we'll let it happen later.
     85   if (force || ((GTK_WIDGET_STATE(widget()) == GTK_STATE_NORMAL) &&
     86       !testing_mouse_hovered_) || ((mode == MODE_STOP) ?
     87           !double_click_timer_.IsRunning() : (visible_mode_ != MODE_STOP))) {
     88     double_click_timer_.Stop();
     89     stop_to_reload_timer_.Stop();
     90     visible_mode_ = mode;
     91 
     92     stop_.set_paint_override(-1);
     93     gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(widget_.get()));
     94 
     95     UpdateThemeButtons();
     96     gtk_widget_queue_draw(widget());
     97   } else if (visible_mode_ != MODE_RELOAD) {
     98     // If you read the views implementation of reload_button.cc, you'll see
     99     // that instead of screwing with paint states, the views implementation
    100     // just changes whether the view is enabled. We can't do that here because
    101     // changing the widget state to GTK_STATE_INSENSITIVE will cause a cascade
    102     // of messages on all its children and will also trigger a synthesized
    103     // leave notification and prevent the real leave notification from turning
    104     // the button back to normal. So instead, override the stop_ paint state
    105     // for chrome-theme mode, and use this as a flag to discard click events.
    106     stop_.set_paint_override(GTK_STATE_INSENSITIVE);
    107 
    108     // Also set the gtk_chrome_button paint state to insensitive to hide
    109     // the border drawn around the stop icon.
    110     gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget_.get()),
    111                                       GTK_STATE_INSENSITIVE);
    112 
    113     // If we're in GTK theme mode, we need to also render the correct icon for
    114     // the stop/insensitive since we won't be using |stop_| to render the icon.
    115     UpdateThemeButtons();
    116 
    117     // Go ahead and change to reload after a bit, which allows repeated reloads
    118     // without moving the mouse.
    119     if (!stop_to_reload_timer_.IsRunning()) {
    120       stop_to_reload_timer_.Start(stop_to_reload_timer_delay_, this,
    121                                   &ReloadButtonGtk::OnStopToReloadTimer);
    122     }
    123   }
    124 }
    125 
    126 ////////////////////////////////////////////////////////////////////////////////
    127 // ReloadButtonGtk, NotificationObserver implementation:
    128 
    129 void ReloadButtonGtk::Observe(NotificationType type,
    130                               const NotificationSource& source,
    131                               const NotificationDetails& /* details */) {
    132   DCHECK(NotificationType::BROWSER_THEME_CHANGED == type);
    133 
    134   GtkThemeService* provider = static_cast<GtkThemeService*>(
    135       Source<ThemeService>(source).ptr());
    136   DCHECK_EQ(provider, theme_service_);
    137   GtkButtonWidth = 0;
    138   UpdateThemeButtons();
    139 }
    140 
    141 ////////////////////////////////////////////////////////////////////////////////
    142 // ReloadButtonGtk, private:
    143 
    144 void ReloadButtonGtk::OnClicked(GtkWidget* /* sender */) {
    145   if (visible_mode_ == MODE_STOP) {
    146     // Do nothing if Stop was disabled due to an attempt to change back to
    147     // RELOAD mode while hovered.
    148     if (stop_.paint_override() == GTK_STATE_INSENSITIVE)
    149       return;
    150 
    151     if (browser_)
    152       browser_->Stop();
    153 
    154     // The user has clicked, so we can feel free to update the button,
    155     // even if the mouse is still hovering.
    156     ChangeMode(MODE_RELOAD, true);
    157   } else if (!double_click_timer_.IsRunning()) {
    158     // Shift-clicking or Ctrl-clicking the reload button means we should ignore
    159     // any cached content.
    160     int command;
    161     GdkModifierType modifier_state;
    162     gtk_get_current_event_state(&modifier_state);
    163     guint modifier_state_uint = modifier_state;
    164     if (modifier_state_uint & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
    165       command = IDC_RELOAD_IGNORING_CACHE;
    166       // Mask off Shift and Control so they don't affect the disposition below.
    167       modifier_state_uint &= ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK);
    168     } else {
    169       command = IDC_RELOAD;
    170     }
    171 
    172     WindowOpenDisposition disposition =
    173         event_utils::DispositionFromEventFlags(modifier_state_uint);
    174     if ((disposition == CURRENT_TAB) && location_bar_) {
    175       // Forcibly reset the location bar, since otherwise it won't discard any
    176       // ongoing user edits, since it doesn't realize this is a user-initiated
    177       // action.
    178       location_bar_->Revert();
    179     }
    180 
    181     // Start a timer - while this timer is running, the reload button cannot be
    182     // changed to a stop button.  We do not set |intended_mode_| to MODE_STOP
    183     // here as the browser will do that when it actually starts loading (which
    184     // may happen synchronously, thus the need to do this before telling the
    185     // browser to execute the reload command).
    186     double_click_timer_.Start(double_click_timer_delay_, this,
    187                               &ReloadButtonGtk::OnDoubleClickTimer);
    188 
    189     if (browser_)
    190       browser_->ExecuteCommandWithDisposition(command, disposition);
    191     ++testing_reload_count_;
    192   }
    193 }
    194 
    195 gboolean ReloadButtonGtk::OnExpose(GtkWidget* widget,
    196                                    GdkEventExpose* e) {
    197   if (theme_service_ && theme_service_->UseGtkTheme())
    198     return FALSE;
    199   return ((visible_mode_ == MODE_RELOAD) ? reload_ : stop_).OnExpose(
    200       widget, e, hover_controller_.GetCurrentValue());
    201 }
    202 
    203 gboolean ReloadButtonGtk::OnLeaveNotify(GtkWidget* /* widget */,
    204                                         GdkEventCrossing* /* event */) {
    205   ChangeMode(intended_mode_, true);
    206   return FALSE;
    207 }
    208 
    209 gboolean ReloadButtonGtk::OnQueryTooltip(GtkWidget* /* sender */,
    210                                          gint /* x */,
    211                                          gint /* y */,
    212                                          gboolean /* keyboard_mode */,
    213                                          GtkTooltip* tooltip) {
    214   // |location_bar_| can be NULL in tests.
    215   if (!location_bar_)
    216     return FALSE;
    217 
    218   gtk_tooltip_set_text(tooltip, l10n_util::GetStringUTF8(
    219       (visible_mode_ == MODE_RELOAD) ?
    220       IDS_TOOLTIP_RELOAD : IDS_TOOLTIP_STOP).c_str());
    221   return TRUE;
    222 }
    223 
    224 void ReloadButtonGtk::UpdateThemeButtons() {
    225   bool use_gtk = theme_service_ && theme_service_->UseGtkTheme();
    226 
    227   if (use_gtk) {
    228     gtk_widget_ensure_style(widget());
    229     GtkIconSet* icon_set = gtk_style_lookup_icon_set(
    230         widget()->style,
    231         (visible_mode_ == MODE_RELOAD) ? GTK_STOCK_REFRESH : GTK_STOCK_STOP);
    232     if (icon_set) {
    233       GtkStateType state = static_cast<GtkStateType>(
    234           GTK_WIDGET_STATE(widget()));
    235       if (visible_mode_ == MODE_STOP && stop_.paint_override() != -1)
    236         state = static_cast<GtkStateType>(stop_.paint_override());
    237 
    238       GdkPixbuf* pixbuf = gtk_icon_set_render_icon(
    239           icon_set,
    240           widget()->style,
    241           gtk_widget_get_direction(widget()),
    242           state,
    243           GTK_ICON_SIZE_SMALL_TOOLBAR,
    244           widget(),
    245           NULL);
    246 
    247       gtk_button_set_image(GTK_BUTTON(widget()),
    248                            gtk_image_new_from_pixbuf(pixbuf));
    249       g_object_unref(pixbuf);
    250     }
    251 
    252     gtk_widget_set_size_request(widget(), -1, -1);
    253     GtkRequisition req;
    254     gtk_widget_size_request(widget(), &req);
    255     GtkButtonWidth = std::max(GtkButtonWidth, req.width);
    256     gtk_widget_set_size_request(widget(), GtkButtonWidth, -1);
    257 
    258     gtk_widget_set_app_paintable(widget(), FALSE);
    259     gtk_widget_set_double_buffered(widget(), TRUE);
    260   } else {
    261     gtk_button_set_image(GTK_BUTTON(widget()), NULL);
    262 
    263     gtk_widget_set_size_request(widget(), reload_.Width(), reload_.Height());
    264 
    265     gtk_widget_set_app_paintable(widget(), TRUE);
    266     // We effectively double-buffer by virtue of having only one image...
    267     gtk_widget_set_double_buffered(widget(), FALSE);
    268   }
    269 
    270   gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(widget()), use_gtk);
    271 }
    272 
    273 void ReloadButtonGtk::OnDoubleClickTimer() {
    274   ChangeMode(intended_mode_, false);
    275 }
    276 
    277 void ReloadButtonGtk::OnStopToReloadTimer() {
    278   ChangeMode(intended_mode_, true);
    279 }
    280