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.
5 #include "chrome/browser/ui/gtk/zoom_bubble_gtk.h"
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"
30 // Pointer to singleton object (NULL if no bubble is open).
31 ZoomBubbleGtk
* g_bubble
= NULL
;
33 // Number of milliseconds the bubble should stay open for if it will auto-close.
34 const int kBubbleCloseDelay
= 1500;
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;
44 void ZoomBubbleGtk::ShowBubble(content::WebContents
* web_contents
,
46 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
47 DCHECK(browser
&& browser
->window() && browser
->fullscreen_controller());
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();
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
58 g_bubble
->auto_close_
== auto_close
&&
59 g_bubble
->bubble_
->anchor_widget() == anchor
) {
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.
68 g_bubble
= new ZoomBubbleGtk(anchor
,
71 browser
->fullscreen_controller());
76 void ZoomBubbleGtk::CloseBubble() {
82 bool ZoomBubbleGtk::IsShowing() {
83 return g_bubble
!= NULL
;
86 ZoomBubbleGtk::ZoomBubbleGtk(GtkWidget
* anchor
,
87 content::WebContents
* web_contents
,
89 FullscreenController
* fullscreen_controller
)
90 : auto_close_(auto_close
),
92 web_contents_(web_contents
) {
93 GtkThemeService
* theme_service
=
94 GtkThemeService::GetFrom(Profile::FromBrowserContext(
95 web_contents_
->GetBrowserContext()));
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
);
102 ZoomController
* zoom_controller
=
103 ZoomController::FromWebContents(web_contents_
);
104 int zoom_percent
= zoom_controller
->zoom_percent();
105 std::string percentage_text
= base::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);
113 GtkWidget
* set_default_button
= gtk_button_new_with_label(
114 l10n_util::GetStringUTF8(IDS_ZOOM_SET_DEFAULT
).c_str());
116 GtkWidget
* alignment
= gtk_alignment_new(0, 0, 1, 1);
117 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment
),
121 ui::kControlSpacing
);
122 gtk_container_add(GTK_CONTAINER(alignment
), set_default_button
);
124 gtk_box_pack_start(GTK_BOX(container
), alignment
, FALSE
, FALSE
, 0);
126 g_signal_connect(set_default_button
, "clicked",
127 G_CALLBACK(&OnSetDefaultLinkClickThunk
), this);
129 gtk_container_set_focus_child(GTK_CONTAINER(container
), NULL
);
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
);
144 g_signal_connect(event_box_
, "destroy",
145 G_CALLBACK(&OnDestroyThunk
), this);
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);
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);
165 chrome::NOTIFICATION_FULLSCREEN_CHANGED
,
166 content::Source
<FullscreenController
>(fullscreen_controller
));
168 StartTimerIfNecessary();
171 ZoomBubbleGtk::~ZoomBubbleGtk() {
172 DCHECK_EQ(g_bubble
, this);
173 // Set singleton pointer to NULL.
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_
),
184 base::UTF16ToUTF8(text
).c_str());
185 StartTimerIfNecessary();
188 void ZoomBubbleGtk::StartTimerIfNecessary() {
189 if (!auto_close_
|| mouse_inside_
)
192 if (timer_
.IsRunning()) {
197 base::TimeDelta::FromMilliseconds(kBubbleCloseDelay
),
199 &ZoomBubbleGtk::Close
);
203 void ZoomBubbleGtk::StopTimerIfNecessary() {
204 if (timer_
.IsRunning())
208 void ZoomBubbleGtk::Close() {
213 void ZoomBubbleGtk::OnDestroy(GtkWidget
* widget
) {
214 // Listen to the destroy signal and delete this instance when it is caught.
218 void ZoomBubbleGtk::OnSetDefaultLinkClick(GtkWidget
* widget
) {
219 double default_zoom_level
= Profile::FromBrowserContext(
220 web_contents_
->GetBrowserContext())->GetPrefs()->GetDouble(
221 prefs::kDefaultZoomLevel
);
222 web_contents_
->SetZoomLevel(default_zoom_level
);
225 gboolean
ZoomBubbleGtk::OnMouseEnter(GtkWidget
* widget
,
226 GdkEventCrossing
* event
) {
227 mouse_inside_
= true;
228 StopTimerIfNecessary();
232 gboolean
ZoomBubbleGtk::OnMouseLeave(GtkWidget
* widget
,
233 GdkEventCrossing
* event
) {
234 mouse_inside_
= false;
235 StartTimerIfNecessary();
239 void ZoomBubbleGtk::Observe(int type
,
240 const content::NotificationSource
& source
,
241 const content::NotificationDetails
& details
) {
242 DCHECK_EQ(type
, chrome::NOTIFICATION_FULLSCREEN_CHANGED
);