| 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 |