Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / tabs / tab_gtk.cc
blob7de94cb5143b8c35c795e66e203502f7a10ff54c
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/tabs/tab_gtk.h"
7 #include <gdk/gdkkeysyms.h>
9 #include "base/bind.h"
10 #include "base/debug/trace_event.h"
11 #include "base/memory/singleton.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/app/chrome_command_ids.h"
14 #include "chrome/browser/ui/gtk/accelerators_gtk.h"
15 #include "chrome/browser/ui/gtk/gtk_input_event_box.h"
16 #include "chrome/browser/ui/gtk/menu_gtk.h"
17 #include "chrome/browser/ui/gtk/tabs/tab_strip_menu_controller.h"
18 #include "chrome/browser/ui/tabs/tab_menu_model.h"
19 #include "chrome/browser/ui/tabs/tab_resources.h"
20 #include "grit/generated_resources.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/dragdrop/gtk_dnd_util.h"
23 #include "ui/base/gtk/scoped_region.h"
24 #include "ui/gfx/path.h"
26 using content::WebContents;
28 namespace {
30 // Returns the width of the title for the current font, in pixels.
31 int GetTitleWidth(gfx::Font* font, base::string16 title) {
32 DCHECK(font);
33 if (title.empty())
34 return 0;
36 return font->GetStringWidth(title);
39 } // namespace
41 class TabGtk::TabGtkObserverHelper {
42 public:
43 explicit TabGtkObserverHelper(TabGtk* tab)
44 : tab_(tab) {
45 base::MessageLoopForUI::current()->AddObserver(tab_);
48 ~TabGtkObserverHelper() {
49 base::MessageLoopForUI::current()->RemoveObserver(tab_);
52 private:
53 TabGtk* tab_;
55 DISALLOW_COPY_AND_ASSIGN(TabGtkObserverHelper);
58 ///////////////////////////////////////////////////////////////////////////////
59 // TabGtk, public:
61 TabGtk::TabGtk(TabDelegate* delegate)
62 : TabRendererGtk(delegate->GetThemeProvider()),
63 delegate_(delegate),
64 closing_(false),
65 dragging_(false),
66 last_mouse_down_(NULL),
67 drag_widget_(NULL),
68 title_width_(0),
69 destroy_factory_(this),
70 drag_end_factory_(this) {
71 event_box_ = gtk_input_event_box_new();
72 g_signal_connect(event_box_, "button-press-event",
73 G_CALLBACK(OnButtonPressEventThunk), this);
74 g_signal_connect(event_box_, "button-release-event",
75 G_CALLBACK(OnButtonReleaseEventThunk), this);
76 g_signal_connect(event_box_, "enter-notify-event",
77 G_CALLBACK(OnEnterNotifyEventThunk), this);
78 g_signal_connect(event_box_, "leave-notify-event",
79 G_CALLBACK(OnLeaveNotifyEventThunk), this);
80 gtk_widget_add_events(event_box_,
81 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
82 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
83 gtk_container_add(GTK_CONTAINER(event_box_), TabRendererGtk::widget());
84 gtk_widget_show_all(event_box_);
87 TabGtk::~TabGtk() {
88 if (drag_widget_) {
89 // Shadow the drag grab so the grab terminates. We could do this using any
90 // widget, |drag_widget_| is just convenient.
91 gtk_grab_add(drag_widget_);
92 gtk_grab_remove(drag_widget_);
93 DestroyDragWidget();
96 if (menu_controller_.get()) {
97 // The menu is showing. Close the menu.
98 menu_controller_->Cancel();
100 // Invoke this so that we hide the highlight.
101 ContextMenuClosed();
105 void TabGtk::Raise() const {
106 UNSHIPPED_TRACE_EVENT0("ui::gtk", "TabGtk::Raise");
108 GdkWindow* window = gtk_input_event_box_get_window(
109 GTK_INPUT_EVENT_BOX(event_box_));
110 gdk_window_raise(window);
111 TabRendererGtk::Raise();
114 gboolean TabGtk::OnButtonPressEvent(GtkWidget* widget, GdkEventButton* event) {
115 // Every button press ensures either a button-release-event or a drag-fail
116 // signal for |widget|.
117 if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
118 // Store whether or not we were selected just now... we only want to be
119 // able to drag foreground tabs, so we don't start dragging the tab if
120 // it was in the background.
121 if (!IsActive()) {
122 if (event->state & GDK_CONTROL_MASK)
123 delegate_->ToggleTabSelection(this);
124 else if (event->state & GDK_SHIFT_MASK)
125 delegate_->ExtendTabSelection(this);
126 else
127 delegate_->ActivateTab(this);
129 // Hook into the message loop to handle dragging.
130 observer_.reset(new TabGtkObserverHelper(this));
132 // Store the button press event, used to initiate a drag.
133 last_mouse_down_ = gdk_event_copy(reinterpret_cast<GdkEvent*>(event));
134 } else if (event->button == 3) {
135 // Only show the context menu if the left mouse button isn't down (i.e.,
136 // the user might want to drag instead).
137 if (!last_mouse_down_) {
138 menu_controller_.reset(delegate()->GetTabStripMenuControllerForTab(this));
139 menu_controller_->RunMenu(gfx::Point(event->x_root, event->y_root),
140 event->time);
144 return TRUE;
147 gboolean TabGtk::OnButtonReleaseEvent(GtkWidget* widget,
148 GdkEventButton* event) {
149 if (event->button == 1) {
150 if (IsActive() && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
151 delegate_->ActivateTab(this);
153 observer_.reset();
155 if (last_mouse_down_) {
156 gdk_event_free(last_mouse_down_);
157 last_mouse_down_ = NULL;
161 GtkAllocation allocation;
162 gtk_widget_get_allocation(widget, &allocation);
164 // Middle mouse up means close the tab, but only if the mouse is over it
165 // (like a button).
166 if (event->button == 2 &&
167 event->x >= 0 && event->y >= 0 &&
168 event->x < allocation.width &&
169 event->y < allocation.height) {
170 // If the user is currently holding the left mouse button down but hasn't
171 // moved the mouse yet, a drag hasn't started yet. In that case, clean up
172 // some state before closing the tab to avoid a crash. Once the drag has
173 // started, we don't get the middle mouse click here.
174 if (last_mouse_down_) {
175 DCHECK(!drag_widget_);
176 observer_.reset();
177 gdk_event_free(last_mouse_down_);
178 last_mouse_down_ = NULL;
180 delegate_->CloseTab(this);
183 return TRUE;
186 gboolean TabGtk::OnDragFailed(GtkWidget* widget, GdkDragContext* context,
187 GtkDragResult result) {
188 bool canceled = (result == GTK_DRAG_RESULT_USER_CANCELLED);
189 EndDrag(canceled);
190 return TRUE;
193 gboolean TabGtk::OnDragButtonReleased(GtkWidget* widget,
194 GdkEventButton* button) {
195 // We always get this event when gtk is releasing the grab and ending the
196 // drag. However, if the user ended the drag with space or enter, we don't
197 // get a follow up event to tell us the drag has finished (either a
198 // drag-failed or a drag-end). So we post a task to manually end the drag.
199 // If GTK+ does send the drag-failed or drag-end event, we cancel the task.
200 base::MessageLoop::current()->PostTask(
201 FROM_HERE,
202 base::Bind(&TabGtk::EndDrag, drag_end_factory_.GetWeakPtr(), false));
203 return TRUE;
206 void TabGtk::OnDragBegin(GtkWidget* widget, GdkDragContext* context) {
207 GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
208 gdk_pixbuf_fill(pixbuf, 0);
209 gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
210 g_object_unref(pixbuf);
213 ///////////////////////////////////////////////////////////////////////////////
214 // TabGtk, MessageLoop::Observer implementation:
216 void TabGtk::WillProcessEvent(GdkEvent* event) {
217 // Nothing to do.
220 void TabGtk::DidProcessEvent(GdkEvent* event) {
221 if (!(event->type == GDK_MOTION_NOTIFY || event->type == GDK_LEAVE_NOTIFY ||
222 event->type == GDK_ENTER_NOTIFY)) {
223 return;
226 if (drag_widget_) {
227 delegate_->ContinueDrag(NULL);
228 return;
231 gint old_x = static_cast<gint>(last_mouse_down_->button.x_root);
232 gint old_y = static_cast<gint>(last_mouse_down_->button.y_root);
233 gdouble new_x;
234 gdouble new_y;
235 gdk_event_get_root_coords(event, &new_x, &new_y);
237 if (gtk_drag_check_threshold(widget(), old_x, old_y,
238 static_cast<gint>(new_x), static_cast<gint>(new_y))) {
239 StartDragging(gfx::Point(
240 static_cast<int>(last_mouse_down_->button.x),
241 static_cast<int>(last_mouse_down_->button.y)));
245 ///////////////////////////////////////////////////////////////////////////////
246 // TabGtk, TabRendererGtk overrides:
248 bool TabGtk::IsActive() const {
249 return delegate_->IsTabActive(this);
252 bool TabGtk::IsSelected() const {
253 return delegate_->IsTabSelected(this);
256 bool TabGtk::IsVisible() const {
257 return gtk_widget_get_visible(event_box_);
260 void TabGtk::SetVisible(bool visible) const {
261 gtk_widget_set_visible(event_box_, visible);
264 void TabGtk::CloseButtonClicked() {
265 delegate_->CloseTab(this);
268 void TabGtk::UpdateData(WebContents* contents, bool app, bool loading_only) {
269 TabRendererGtk::UpdateData(contents, app, loading_only);
270 // Cache the title width so we don't recalculate it every time the tab is
271 // resized.
272 title_width_ = GetTitleWidth(title_font(), GetTitle());
273 UpdateTooltipState();
276 void TabGtk::SetBounds(const gfx::Rect& bounds) {
277 TabRendererGtk::SetBounds(bounds);
279 if (gtk_input_event_box_get_window(GTK_INPUT_EVENT_BOX(event_box_))) {
280 gfx::Path mask;
281 TabResources::GetHitTestMask(bounds.width(), bounds.height(), false, &mask);
282 ui::ScopedRegion region(mask.CreateNativeRegion());
283 gdk_window_input_shape_combine_region(
284 gtk_input_event_box_get_window(GTK_INPUT_EVENT_BOX(event_box_)),
285 region.Get(),
286 0, 0);
289 UpdateTooltipState();
292 ///////////////////////////////////////////////////////////////////////////////
293 // TabGtk, private:
295 void TabGtk::ContextMenuClosed() {
296 delegate()->StopAllHighlighting();
297 menu_controller_.reset();
300 void TabGtk::UpdateTooltipState() {
301 // Note: This method can be called several times per second for various
302 // reasons (e.g., navigation/loading state changes, tab media indicator
303 // updates, and so on). However, we must avoid calling the
304 // gtk_widget_set_XXX() methods if there is no actual change in state.
305 // Otherwise, GTK will continuously re-hide *all* tooltips throughout the
306 // browser in response! http://crbug.com/333002
308 // Only show the tooltip if the title is truncated.
309 if (title_width_ > title_bounds().width()) {
310 const std::string utf8_title = base::UTF16ToUTF8(GetTitle());
311 if (gtk_widget_get_has_tooltip(widget())) {
312 gchar* const current_tooltip = gtk_widget_get_tooltip_text(widget());
313 if (current_tooltip) {
314 const bool title_unchanged = (utf8_title == current_tooltip);
315 g_free(current_tooltip);
316 if (title_unchanged)
317 return;
320 gtk_widget_set_tooltip_text(widget(), utf8_title.c_str());
321 } else {
322 if (gtk_widget_get_has_tooltip(widget()))
323 gtk_widget_set_has_tooltip(widget(), FALSE);
327 void TabGtk::CreateDragWidget() {
328 DCHECK(!drag_widget_);
329 drag_widget_ = gtk_invisible_new();
330 g_signal_connect(drag_widget_, "drag-failed",
331 G_CALLBACK(OnDragFailedThunk), this);
332 g_signal_connect(drag_widget_, "button-release-event",
333 G_CALLBACK(OnDragButtonReleasedThunk), this);
334 g_signal_connect_after(drag_widget_, "drag-begin",
335 G_CALLBACK(OnDragBeginThunk), this);
338 void TabGtk::DestroyDragWidget() {
339 if (drag_widget_) {
340 gtk_widget_destroy(drag_widget_);
341 drag_widget_ = NULL;
345 void TabGtk::StartDragging(gfx::Point drag_offset) {
346 // If the drag is processed after the selection change it's possible
347 // that the tab has been deselected, in which case we don't want to drag.
348 if (!IsSelected())
349 return;
351 CreateDragWidget();
353 GtkTargetList* list = ui::GetTargetListFromCodeMask(ui::CHROME_TAB);
354 gtk_drag_begin(drag_widget_, list, GDK_ACTION_MOVE,
355 1, // Drags are always initiated by the left button.
356 last_mouse_down_);
357 // gtk_drag_begin adds a reference to list, so unref it here.
358 gtk_target_list_unref(list);
359 delegate_->MaybeStartDrag(this, drag_offset);
362 void TabGtk::EndDrag(bool canceled) {
363 // Make sure we only run EndDrag once by canceling any tasks that want
364 // to call EndDrag.
365 drag_end_factory_.InvalidateWeakPtrs();
367 // We must let gtk clean up after we handle the drag operation, otherwise
368 // there will be outstanding references to the drag widget when we try to
369 // destroy it.
370 base::MessageLoop::current()->PostTask(
371 FROM_HERE,
372 base::Bind(&TabGtk::DestroyDragWidget, destroy_factory_.GetWeakPtr()));
374 if (last_mouse_down_) {
375 gdk_event_free(last_mouse_down_);
376 last_mouse_down_ = NULL;
379 // Notify the drag helper that we're done with any potential drag operations.
380 // Clean up the drag helper, which is re-created on the next mouse press.
381 delegate_->EndDrag(canceled);
383 observer_.reset();