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