OLD | NEW |
| (Empty) |
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 = 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); | |
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_), | |
184 base::UTF16ToUTF8(text).c_str()); | |
185 StartTimerIfNecessary(); | |
186 } | |
187 | |
188 void ZoomBubbleGtk::StartTimerIfNecessary() { | |
189 if (!auto_close_ || mouse_inside_) | |
190 return; | |
191 | |
192 if (timer_.IsRunning()) { | |
193 timer_.Reset(); | |
194 } else { | |
195 timer_.Start( | |
196 FROM_HERE, | |
197 base::TimeDelta::FromMilliseconds(kBubbleCloseDelay), | |
198 this, | |
199 &ZoomBubbleGtk::Close); | |
200 } | |
201 } | |
202 | |
203 void ZoomBubbleGtk::StopTimerIfNecessary() { | |
204 if (timer_.IsRunning()) | |
205 timer_.Stop(); | |
206 } | |
207 | |
208 void ZoomBubbleGtk::Close() { | |
209 DCHECK(bubble_); | |
210 bubble_->Close(); | |
211 } | |
212 | |
213 void ZoomBubbleGtk::OnDestroy(GtkWidget* widget) { | |
214 // Listen to the destroy signal and delete this instance when it is caught. | |
215 delete this; | |
216 } | |
217 | |
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); | |
223 } | |
224 | |
225 gboolean ZoomBubbleGtk::OnMouseEnter(GtkWidget* widget, | |
226 GdkEventCrossing* event) { | |
227 mouse_inside_ = true; | |
228 StopTimerIfNecessary(); | |
229 return FALSE; | |
230 } | |
231 | |
232 gboolean ZoomBubbleGtk::OnMouseLeave(GtkWidget* widget, | |
233 GdkEventCrossing* event) { | |
234 mouse_inside_ = false; | |
235 StartTimerIfNecessary(); | |
236 return FALSE; | |
237 } | |
238 | |
239 void ZoomBubbleGtk::Observe(int type, | |
240 const content::NotificationSource& source, | |
241 const content::NotificationDetails& details) { | |
242 DCHECK_EQ(type, chrome::NOTIFICATION_FULLSCREEN_CHANGED); | |
243 CloseBubble(); | |
244 } | |
OLD | NEW |