Home | History | Annotate | Download | only in gtk
      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/gtk/zoom_bubble_gtk.h"
      6 
      7 #include "base/i18n/rtl.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/profiles/profile.h"
     12 #include "chrome/browser/ui/browser.h"
     13 #include "chrome/browser/ui/browser_finder.h"
     14 #include "chrome/browser/ui/browser_window.h"
     15 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     16 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
     17 #include "chrome/browser/ui/zoom/zoom_controller.h"
     18 #include "chrome/common/pref_names.h"
     19 #include "content/public/browser/notification_details.h"
     20 #include "content/public/browser/notification_source.h"
     21 #include "content/public/browser/render_view_host.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "grit/generated_resources.h"
     24 #include "ui/base/gtk/gtk_hig_constants.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 #include "ui/gfx/rect.h"
     27 
     28 namespace {
     29 
     30 // Pointer to singleton object (NULL if no bubble is open).
     31 ZoomBubbleGtk* g_bubble = NULL;
     32 
     33 // Number of milliseconds the bubble should stay open for if it will auto-close.
     34 const int kBubbleCloseDelay = 1500;
     35 
     36 // Need to manually set anchor width and height to ensure that the bubble shows
     37 // in the correct spot the first time it is displayed when no icon is present.
     38 const int kBubbleAnchorWidth = 20;
     39 const int kBubbleAnchorHeight = 25;
     40 
     41 }  // namespace
     42 
     43 // static
     44 void ZoomBubbleGtk::ShowBubble(content::WebContents* web_contents,
     45                                bool auto_close) {
     46   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
     47   DCHECK(browser && browser->window() && browser->fullscreen_controller());
     48 
     49   LocationBar* location_bar = browser->window()->GetLocationBar();
     50   GtkWidget* anchor = browser->window()->IsFullscreen() ?
     51       GTK_WIDGET(browser->window()->GetNativeWindow()) :
     52       static_cast<LocationBarViewGtk*>(location_bar)->zoom_widget();
     53 
     54   // If the bubble is already showing and its |auto_close_| value is equal to
     55   // |auto_close|, the bubble can be reused and only the label text needs to
     56   // be updated.
     57   if (g_bubble &&
     58       g_bubble->auto_close_ == auto_close &&
     59       g_bubble->bubble_->anchor_widget() == anchor) {
     60     g_bubble->Refresh();
     61   } else {
     62     // If the bubble is already showing but its |auto_close_| value is not equal
     63     // to |auto_close|, the bubble's focus properties must change, so the
     64     // current bubble must be closed and a new one created.
     65     CloseBubble();
     66     DCHECK(!g_bubble);
     67 
     68     g_bubble = new ZoomBubbleGtk(anchor,
     69                                  web_contents,
     70                                  auto_close,
     71                                  browser->fullscreen_controller());
     72   }
     73 }
     74 
     75 // static
     76 void ZoomBubbleGtk::CloseBubble() {
     77   if (g_bubble)
     78     g_bubble->Close();
     79 }
     80 
     81 // static
     82 bool ZoomBubbleGtk::IsShowing() {
     83   return g_bubble != NULL;
     84 }
     85 
     86 ZoomBubbleGtk::ZoomBubbleGtk(GtkWidget* anchor,
     87                              content::WebContents* web_contents,
     88                              bool auto_close,
     89                              FullscreenController* fullscreen_controller)
     90     : auto_close_(auto_close),
     91       mouse_inside_(false),
     92       web_contents_(web_contents) {
     93   GtkThemeService* theme_service =
     94       GtkThemeService::GetFrom(Profile::FromBrowserContext(
     95           web_contents_->GetBrowserContext()));
     96 
     97   event_box_ = gtk_event_box_new();
     98   gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_), FALSE);
     99   GtkWidget* container = gtk_vbox_new(FALSE, 0);
    100   gtk_container_add(GTK_CONTAINER(event_box_), container);
    101 
    102   ZoomController* zoom_controller =
    103       ZoomController::FromWebContents(web_contents_);
    104   int zoom_percent = zoom_controller->zoom_percent();
    105   std::string percentage_text = UTF16ToUTF8(l10n_util::GetStringFUTF16Int(
    106       IDS_TOOLTIP_ZOOM, zoom_percent));
    107   label_ = theme_service->BuildLabel(percentage_text, ui::kGdkBlack);
    108   gtk_widget_modify_font(label_, pango_font_description_from_string("13"));
    109   gtk_misc_set_padding(GTK_MISC(label_),
    110                        ui::kControlSpacing, ui::kControlSpacing);
    111   gtk_box_pack_start(GTK_BOX(container), label_, FALSE, FALSE, 0);
    112 
    113   GtkWidget* set_default_button = gtk_button_new_with_label(
    114       l10n_util::GetStringUTF8(IDS_ZOOM_SET_DEFAULT).c_str());
    115 
    116   GtkWidget* alignment = gtk_alignment_new(0, 0, 1, 1);
    117   gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
    118                             0,
    119                             ui::kControlSpacing,
    120                             ui::kControlSpacing,
    121                             ui::kControlSpacing);
    122   gtk_container_add(GTK_CONTAINER(alignment), set_default_button);
    123 
    124   gtk_box_pack_start(GTK_BOX(container), alignment, FALSE, FALSE, 0);
    125 
    126   g_signal_connect(set_default_button, "clicked",
    127                    G_CALLBACK(&OnSetDefaultLinkClickThunk), this);
    128 
    129   gtk_container_set_focus_child(GTK_CONTAINER(container), NULL);
    130 
    131   gfx::Rect rect = gfx::Rect(kBubbleAnchorWidth, kBubbleAnchorHeight);
    132   BubbleGtk::FrameStyle frame_style = gtk_widget_is_toplevel(anchor) ?
    133       BubbleGtk::FIXED_TOP_RIGHT : BubbleGtk::ANCHOR_TOP_MIDDLE;
    134   int bubble_options = BubbleGtk::MATCH_SYSTEM_THEME | BubbleGtk::POPUP_WINDOW;
    135   bubble_ = BubbleGtk::Show(anchor, &rect, event_box_, frame_style,
    136       auto_close ? bubble_options : bubble_options | BubbleGtk::GRAB_INPUT,
    137       theme_service, NULL);
    138 
    139   if (!bubble_) {
    140     NOTREACHED();
    141     return;
    142   }
    143 
    144   g_signal_connect(event_box_, "destroy",
    145                    G_CALLBACK(&OnDestroyThunk), this);
    146 
    147   if (auto_close_) {
    148     // If this is an auto-closing bubble, listen to leave/enter to keep the
    149     // bubble alive if the mouse stays anywhere inside the bubble.
    150     g_signal_connect_after(event_box_, "enter-notify-event",
    151                            G_CALLBACK(&OnMouseEnterThunk), this);
    152     g_signal_connect(event_box_, "leave-notify-event",
    153                      G_CALLBACK(&OnMouseLeaveThunk), this);
    154 
    155     // This is required as a leave is fired when the mouse goes from inside the
    156     // bubble's container to inside the set default button.
    157     gtk_widget_add_events(set_default_button, GDK_ENTER_NOTIFY_MASK);
    158     g_signal_connect_after(set_default_button, "enter-notify-event",
    159                            G_CALLBACK(&OnMouseEnterThunk), this);
    160     g_signal_connect(set_default_button, "leave-notify-event",
    161                      G_CALLBACK(&OnMouseLeaveThunk), this);
    162   }
    163 
    164   registrar_.Add(this,
    165                  chrome::NOTIFICATION_FULLSCREEN_CHANGED,
    166                  content::Source<FullscreenController>(fullscreen_controller));
    167 
    168   StartTimerIfNecessary();
    169 }
    170 
    171 ZoomBubbleGtk::~ZoomBubbleGtk() {
    172   DCHECK_EQ(g_bubble, this);
    173   // Set singleton pointer to NULL.
    174   g_bubble = NULL;
    175 }
    176 
    177 void ZoomBubbleGtk::Refresh() {
    178   ZoomController* zoom_controller =
    179       ZoomController::FromWebContents(web_contents_);
    180   int zoom_percent = zoom_controller->zoom_percent();
    181   string16 text =
    182       l10n_util::GetStringFUTF16Int(IDS_TOOLTIP_ZOOM, zoom_percent);
    183   gtk_label_set_text(GTK_LABEL(g_bubble->label_), UTF16ToUTF8(text).c_str());
    184   StartTimerIfNecessary();
    185 }
    186 
    187 void ZoomBubbleGtk::StartTimerIfNecessary() {
    188   if (!auto_close_ || mouse_inside_)
    189     return;
    190 
    191   if (timer_.IsRunning()) {
    192     timer_.Reset();
    193   } else {
    194     timer_.Start(
    195         FROM_HERE,
    196         base::TimeDelta::FromMilliseconds(kBubbleCloseDelay),
    197         this,
    198         &ZoomBubbleGtk::Close);
    199   }
    200 }
    201 
    202 void ZoomBubbleGtk::StopTimerIfNecessary() {
    203   if (timer_.IsRunning())
    204     timer_.Stop();
    205 }
    206 
    207 void ZoomBubbleGtk::Close() {
    208   DCHECK(bubble_);
    209   bubble_->Close();
    210 }
    211 
    212 void ZoomBubbleGtk::OnDestroy(GtkWidget* widget) {
    213   // Listen to the destroy signal and delete this instance when it is caught.
    214   delete this;
    215 }
    216 
    217 void ZoomBubbleGtk::OnSetDefaultLinkClick(GtkWidget* widget) {
    218   double default_zoom_level = Profile::FromBrowserContext(
    219       web_contents_->GetBrowserContext())->GetPrefs()->GetDouble(
    220           prefs::kDefaultZoomLevel);
    221   web_contents_->GetRenderViewHost()->SetZoomLevel(default_zoom_level);
    222 }
    223 
    224 gboolean ZoomBubbleGtk::OnMouseEnter(GtkWidget* widget,
    225                                      GdkEventCrossing* event) {
    226   mouse_inside_ = true;
    227   StopTimerIfNecessary();
    228   return FALSE;
    229 }
    230 
    231 gboolean ZoomBubbleGtk::OnMouseLeave(GtkWidget* widget,
    232                                      GdkEventCrossing* event) {
    233   mouse_inside_ = false;
    234   StartTimerIfNecessary();
    235   return FALSE;
    236 }
    237 
    238 void ZoomBubbleGtk::Observe(int type,
    239                             const content::NotificationSource& source,
    240                             const content::NotificationDetails& details) {
    241   DCHECK_EQ(type, chrome::NOTIFICATION_FULLSCREEN_CHANGED);
    242   CloseBubble();
    243 }
    244