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 base::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_->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