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/dragged_view_gtk.h"
11 #include "base/debug/trace_event.h"
12 #include "base/i18n/rtl.h"
13 #include "base/stl_util.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/themes/theme_service.h"
17 #include "chrome/browser/themes/theme_service_factory.h"
18 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
19 #include "chrome/browser/ui/gtk/gtk_util.h"
20 #include "chrome/browser/ui/gtk/tabs/drag_data.h"
21 #include "chrome/browser/ui/gtk/tabs/tab_renderer_gtk.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "third_party/skia/include/core/SkShader.h"
26 #include "ui/base/gtk/gtk_screen_util.h"
27 #include "ui/base/x/x11_util.h"
28 #include "ui/gfx/gtk_util.h"
30 using content::WebContents
;
34 // The size of the dragged window frame.
35 const int kDragFrameBorderSize
= 1;
36 const int kTwiceDragFrameBorderSize
= 2 * kDragFrameBorderSize
;
38 // Used to scale the dragged window sizes.
39 const float kScalingFactor
= 0.5;
41 const int kAnimateToBoundsDurationMs
= 150;
43 const gdouble kTransparentAlpha
= (200.0f
/ 255.0f
);
44 const gdouble kOpaqueAlpha
= 1.0f
;
45 const double kDraggedTabBorderColor
[] = { 103.0 / 0xff,
51 ////////////////////////////////////////////////////////////////////////////////
52 // DraggedViewGtk, public:
54 DraggedViewGtk::DraggedViewGtk(DragData
* drag_data
,
55 const gfx::Point
& mouse_tab_offset
,
56 const gfx::Size
& contents_size
)
57 : drag_data_(drag_data
),
61 parent_window_width_(-1),
62 mouse_tab_offset_(mouse_tab_offset
),
63 attached_tab_size_(TabRendererGtk::GetMinimumSelectedSize()),
64 contents_size_(contents_size
),
65 close_animation_(this) {
66 std::vector
<WebContents
*> data_sources(drag_data_
->GetDraggedTabsContents());
67 for (size_t i
= 0; i
< data_sources
.size(); i
++) {
68 renderers_
.push_back(new TabRendererGtk(GtkThemeService::GetFrom(
69 Profile::FromBrowserContext(data_sources
[i
]->GetBrowserContext()))));
72 for (size_t i
= 0; i
< drag_data_
->size(); i
++) {
73 WebContents
* web_contents
= drag_data_
->get(i
)->contents_
;
74 renderers_
[i
]->UpdateData(
76 extensions::TabHelper::FromWebContents(web_contents
)->is_app(),
77 false); // loading_only
78 renderers_
[i
]->set_is_active(
79 static_cast<int>(i
) == drag_data_
->source_tab_index());
82 container_
= gtk_window_new(GTK_WINDOW_POPUP
);
83 SetContainerColorMap();
84 gtk_widget_set_app_paintable(container_
, TRUE
);
85 g_signal_connect(container_
, "expose-event", G_CALLBACK(OnExposeThunk
), this);
86 gtk_widget_add_events(container_
, GDK_STRUCTURE_MASK
);
88 // We contain the tab renderer in a GtkFixed in order to maintain the
89 // requested size. Otherwise, the widget will fill the entire window and
90 // cause a crash when rendering because the bounds don't match our images.
91 fixed_
= gtk_fixed_new();
92 for (size_t i
= 0; i
< renderers_
.size(); i
++)
93 gtk_fixed_put(GTK_FIXED(fixed_
), renderers_
[i
]->widget(), 0, 0);
95 gtk_container_add(GTK_CONTAINER(container_
), fixed_
);
96 gtk_widget_show_all(container_
);
99 DraggedViewGtk::~DraggedViewGtk() {
100 gtk_widget_destroy(container_
);
101 STLDeleteElements(&renderers_
);
104 void DraggedViewGtk::MoveDetachedTo(const gfx::Point
& screen_point
) {
106 gfx::Point distance_from_origin
=
107 GetDistanceFromTabStripOriginToMousePointer();
108 int y
= screen_point
.y() - ScaleValue(distance_from_origin
.y());
109 int x
= screen_point
.x() - ScaleValue(distance_from_origin
.x());
110 gtk_window_move(GTK_WINDOW(container_
), x
, y
);
113 void DraggedViewGtk::MoveAttachedTo(const gfx::Point
& tabstrip_point
) {
115 int x
= tabstrip_point
.x() + GetWidthInTabStripUpToMousePointer() -
116 ScaleValue(GetWidthInTabStripUpToMousePointer());
117 int y
= tabstrip_point
.y() + mouse_tab_offset_
.y() -
118 ScaleValue(mouse_tab_offset_
.y());
119 gtk_window_move(GTK_WINDOW(container_
), x
, y
);
122 gfx::Point
DraggedViewGtk::GetDistanceFromTabStripOriginToMousePointer() {
123 gfx::Point
start_point(GetWidthInTabStripUpToMousePointer(),
124 mouse_tab_offset_
.y());
125 if (base::i18n::IsRTL())
126 start_point
.Offset(parent_window_width_
- GetTotalWidthInTabStrip(), 0);
130 void DraggedViewGtk::Attach(
131 int normal_width
, int mini_width
, int window_width
) {
133 parent_window_width_
= window_width
;
134 normal_width_
= normal_width
;
135 mini_width_
= mini_width
;
137 int dragged_tab_width
=
138 drag_data_
->GetSourceTabData()->mini_
? mini_width
: normal_width
;
140 Resize(dragged_tab_width
);
142 if (ui::IsScreenComposited()) {
143 GdkWindow
* gdk_window
= gtk_widget_get_window(container_
);
144 gdk_window_set_opacity(gdk_window
, kOpaqueAlpha
);
148 void DraggedViewGtk::Resize(int width
) {
149 attached_tab_size_
.set_width(width
);
153 void DraggedViewGtk::Detach() {
157 if (ui::IsScreenComposited()) {
158 GdkWindow
* gdk_window
= gtk_widget_get_window(container_
);
159 gdk_window_set_opacity(gdk_window
, kTransparentAlpha
);
163 void DraggedViewGtk::Update() {
164 gtk_widget_queue_draw(container_
);
167 int DraggedViewGtk::GetWidthInTabStripFromTo(int from
, int to
) {
168 DCHECK(from
<= static_cast<int>(drag_data_
->size()));
169 DCHECK(to
<= static_cast<int>(drag_data_
->size()));
171 // TODO(dpapad): Get 16 from TabStripGtk::kTabHOffset.
172 int mini_tab_count
= 0, non_mini_tab_count
= 0;
173 drag_data_
->GetNumberOfMiniNonMiniTabs(from
, to
,
174 &mini_tab_count
, &non_mini_tab_count
);
175 int width
= non_mini_tab_count
* static_cast<int>(floor(normal_width_
+ 0.5))
176 + mini_tab_count
* mini_width_
- std::max(to
- from
- 1, 0) * 16;
180 int DraggedViewGtk::GetTotalWidthInTabStrip() {
181 return GetWidthInTabStripFromTo(0, drag_data_
->size());
184 int DraggedViewGtk::GetWidthInTabStripUpToSourceTab() {
185 if (!base::i18n::IsRTL()) {
186 return GetWidthInTabStripFromTo(0, drag_data_
->source_tab_index());
188 return GetWidthInTabStripFromTo(
189 drag_data_
->source_tab_index() + 1, drag_data_
->size());
193 int DraggedViewGtk::GetWidthInTabStripUpToMousePointer() {
194 int width
= GetWidthInTabStripUpToSourceTab() + mouse_tab_offset_
.x();
195 if (!base::i18n::IsRTL() && drag_data_
->source_tab_index() > 0) {
197 } else if (base::i18n::IsRTL() &&
198 drag_data_
->source_tab_index() <
199 static_cast<int>(drag_data_
->size()) - 1) {
205 void DraggedViewGtk::AnimateToBounds(const gfx::Rect
& bounds
,
206 const base::Closure
& callback
) {
207 animation_callback_
= callback
;
209 gint x
, y
, width
, height
;
210 GdkWindow
* gdk_window
= gtk_widget_get_window(container_
);
211 gdk_window_get_origin(gdk_window
, &x
, &y
);
212 gdk_window_get_geometry(gdk_window
, NULL
, NULL
,
213 &width
, &height
, NULL
);
215 animation_start_bounds_
= gfx::Rect(x
, y
, width
, height
);
216 animation_end_bounds_
= bounds
;
218 close_animation_
.SetSlideDuration(kAnimateToBoundsDurationMs
);
219 close_animation_
.SetTweenType(gfx::Tween::EASE_OUT
);
220 if (!close_animation_
.IsShowing()) {
221 close_animation_
.Reset();
222 close_animation_
.Show();
226 ////////////////////////////////////////////////////////////////////////////////
227 // DraggedViewGtk, gfx::AnimationDelegate implementation:
229 void DraggedViewGtk::AnimationProgressed(const gfx::Animation
* animation
) {
230 int delta_x
= (animation_end_bounds_
.x() - animation_start_bounds_
.x());
231 int x
= animation_start_bounds_
.x() +
232 static_cast<int>(delta_x
* animation
->GetCurrentValue());
233 int y
= animation_end_bounds_
.y();
234 GdkWindow
* gdk_window
= gtk_widget_get_window(container_
);
235 gdk_window_move(gdk_window
, x
, y
);
238 void DraggedViewGtk::AnimationEnded(const gfx::Animation
* animation
) {
239 animation_callback_
.Run();
242 void DraggedViewGtk::AnimationCanceled(const gfx::Animation
* animation
) {
243 AnimationEnded(animation
);
246 ////////////////////////////////////////////////////////////////////////////////
247 // DraggedViewGtk, private:
249 void DraggedViewGtk::Layout() {
251 for (size_t i
= 0; i
< renderers_
.size(); i
++) {
252 gfx::Rect
rect(GetPreferredSize());
253 rect
.set_width(GetAttachedTabWidthAt(i
));
254 renderers_
[i
]->SetBounds(rect
);
258 if (base::i18n::IsRTL())
259 left
= GetPreferredSize().width() - attached_tab_size_
.width();
261 // The renderer_'s width should be attached_tab_size_.width() in both LTR
262 // and RTL locales. Wrong width will cause the wrong positioning of the tab
263 // view in dragging. Please refer to http://crbug.com/6223 for details.
264 renderers_
[drag_data_
->source_tab_index()]->SetBounds(
265 gfx::Rect(left
, 0, attached_tab_size_
.width(),
266 attached_tab_size_
.height()));
270 gfx::Size
DraggedViewGtk::GetPreferredSize() {
272 gfx::Size
preferred_size(attached_tab_size_
);
273 preferred_size
.set_width(GetTotalWidthInTabStrip());
274 return preferred_size
;
277 int width
= std::max(attached_tab_size_
.width(), contents_size_
.width()) +
278 kTwiceDragFrameBorderSize
;
279 int height
= attached_tab_size_
.height() + kDragFrameBorderSize
+
280 contents_size_
.height();
281 return gfx::Size(width
, height
);
284 void DraggedViewGtk::ResizeContainer() {
285 gfx::Size size
= GetPreferredSize();
286 gtk_window_resize(GTK_WINDOW(container_
),
287 ScaleValue(size
.width()), ScaleValue(size
.height()));
291 int DraggedViewGtk::ScaleValue(int value
) {
292 return attached_
? value
: static_cast<int>(value
* kScalingFactor
);
295 gfx::Rect
DraggedViewGtk::bounds() const {
296 gint x
, y
, width
, height
;
297 gtk_window_get_position(GTK_WINDOW(container_
), &x
, &y
);
298 gtk_window_get_size(GTK_WINDOW(container_
), &width
, &height
);
299 return gfx::Rect(x
, y
, width
, height
);
302 int DraggedViewGtk::GetAttachedTabWidthAt(int index
) {
303 return drag_data_
->get(index
)->mini_
? mini_width_
: normal_width_
;
306 void DraggedViewGtk::SetContainerColorMap() {
307 GdkScreen
* screen
= gtk_widget_get_screen(container_
);
308 GdkColormap
* colormap
= gdk_screen_get_rgba_colormap(screen
);
310 // If rgba is not available, use rgb instead.
312 colormap
= gdk_screen_get_rgb_colormap(screen
);
314 gtk_widget_set_colormap(container_
, colormap
);
317 void DraggedViewGtk::SetContainerTransparency() {
318 cairo_t
* cairo_context
= gdk_cairo_create(gtk_widget_get_window(container_
));
322 // Make the background of the dragged tab window fully transparent. All of
323 // the content of the window (child widgets) will be completely opaque.
324 gfx::Size size
= bounds().size();
325 cairo_scale(cairo_context
, static_cast<double>(size
.width()),
326 static_cast<double>(size
.height()));
327 cairo_set_source_rgba(cairo_context
, 1.0f
, 1.0f
, 1.0f
, 0.0f
);
328 cairo_set_operator(cairo_context
, CAIRO_OPERATOR_SOURCE
);
329 cairo_paint(cairo_context
);
330 cairo_destroy(cairo_context
);
333 void DraggedViewGtk::SetContainerShapeMask() {
334 // Create a 1bpp bitmap the size of |container_|.
335 gfx::Size
size(GetPreferredSize());
336 GdkPixmap
* pixmap
= gdk_pixmap_new(NULL
, size
.width(), size
.height(), 1);
337 cairo_t
* cairo_context
= gdk_cairo_create(GDK_DRAWABLE(pixmap
));
339 // Set the transparency.
340 cairo_set_source_rgba(cairo_context
, 1.0f
, 1.0f
, 1.0f
, 0.0f
);
342 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will
343 // be opaque in the container window.
345 cairo_scale(cairo_context
, kScalingFactor
, kScalingFactor
);
346 for (size_t i
= 0; i
< renderers_
.size(); i
++) {
347 if (static_cast<int>(i
) == 0)
348 cairo_set_operator(cairo_context
, CAIRO_OPERATOR_SOURCE
);
350 cairo_set_operator(cairo_context
, CAIRO_OPERATOR_OVER
);
352 GtkAllocation allocation
;
353 gtk_widget_get_allocation(container_
, &allocation
);
354 PaintTab(i
, container_
, cairo_context
, allocation
.width
);
358 // Make the render area depiction opaque (leaving enough room for the
360 cairo_identity_matrix(cairo_context
);
361 // On Lucid running VNC, the X server will reject RGBA (1,1,1,1) as an
362 // invalid value below in gdk_window_shape_combine_mask(). Using (0,0,0,1)
363 // instead. The value doesn't really matter, as long as the alpha is not 0.
364 cairo_set_source_rgba(cairo_context
, 0.0f
, 0.0f
, 0.0f
, 1.0f
);
365 int tab_height
= static_cast<int>(
366 kScalingFactor
* renderers_
[drag_data_
->source_tab_index()]->height() -
367 kDragFrameBorderSize
);
368 cairo_rectangle(cairo_context
,
370 size
.width(), size
.height() - tab_height
);
371 cairo_fill(cairo_context
);
374 cairo_destroy(cairo_context
);
376 // Set the shape mask.
377 GdkWindow
* gdk_window
= gtk_widget_get_window(container_
);
378 gdk_window_shape_combine_mask(gdk_window
, pixmap
, 0, 0);
379 g_object_unref(pixmap
);
382 gboolean
DraggedViewGtk::OnExpose(GtkWidget
* widget
, GdkEventExpose
* event
) {
383 TRACE_EVENT0("ui::gtk", "DraggedViewGtk::OnExpose");
385 if (ui::IsScreenComposited())
386 SetContainerTransparency();
388 SetContainerShapeMask();
390 // Only used when not attached.
391 int tab_height
= static_cast<int>(
392 kScalingFactor
* renderers_
[drag_data_
->source_tab_index()]->height());
394 GtkAllocation allocation
;
395 gtk_widget_get_allocation(widget
, &allocation
);
397 // Draw the render area.
399 content::RenderWidgetHost
* render_widget_host
=
400 drag_data_
->GetSourceWebContents()->GetRenderViewHost();
402 // This leaves room for the border.
403 gfx::Rect
dest_rect(kDragFrameBorderSize
, tab_height
,
404 allocation
.width
- kTwiceDragFrameBorderSize
,
405 allocation
.height
- tab_height
-
406 kDragFrameBorderSize
);
407 render_widget_host
->CopyFromBackingStoreToGtkWindow(
408 dest_rect
, GDK_DRAWABLE(gtk_widget_get_window(widget
)));
411 cairo_t
* cr
= gdk_cairo_create(gtk_widget_get_window(widget
));
414 cairo_set_line_width(cr
, kDragFrameBorderSize
);
415 cairo_set_source_rgb(cr
, kDraggedTabBorderColor
[0],
416 kDraggedTabBorderColor
[1],
417 kDraggedTabBorderColor
[2]);
418 // |offset| is the distance from the edge of the image to the middle of
420 double offset
= kDragFrameBorderSize
/ 2.0 - 0.5;
421 double left_x
= offset
;
422 double top_y
= tab_height
- kDragFrameBorderSize
+ offset
;
423 double right_x
= allocation
.width
- offset
;
424 double bottom_y
= allocation
.height
- offset
;
426 cairo_move_to(cr
, left_x
, top_y
);
427 cairo_line_to(cr
, left_x
, bottom_y
);
428 cairo_line_to(cr
, right_x
, bottom_y
);
429 cairo_line_to(cr
, right_x
, top_y
);
430 cairo_line_to(cr
, left_x
, top_y
);
436 cairo_scale(cr
, kScalingFactor
, kScalingFactor
);
437 // Painting all but the active tab first, from last to first.
438 for (int i
= renderers_
.size() - 1; i
>= 0; i
--) {
439 if (i
== drag_data_
->source_tab_index())
441 PaintTab(i
, widget
, cr
, allocation
.width
);
443 // Painting the active tab last, so that it appears on top.
444 PaintTab(drag_data_
->source_tab_index(), widget
, cr
,
449 // We've already drawn the tab, so don't propagate the expose-event signal.
453 void DraggedViewGtk::PaintTab(int index
, GtkWidget
* widget
, cairo_t
* cr
,
455 renderers_
[index
]->set_mini(drag_data_
->get(index
)->mini_
);
456 cairo_surface_t
* surface
= renderers_
[index
]->PaintToSurface(widget
, cr
);
459 if (!base::i18n::IsRTL()) {
460 paint_at
= std::max(GetWidthInTabStripFromTo(0, index
) - 16, 0);
462 paint_at
= GetTotalWidthInTabStrip() -
463 GetWidthInTabStripFromTo(0, index
+ 1);
465 paint_at
= widget_width
/ kScalingFactor
-
466 GetWidthInTabStripFromTo(0, index
+ 1);
470 cairo_set_source_surface(cr
, surface
, paint_at
, 0);
472 cairo_surface_destroy(surface
);