1 // Copyright (c) 2011 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/dragged_tab_gtk.h"
6
7 #include <gdk/gdk.h>
8
9 #include <algorithm>
10
11 #include "base/i18n/rtl.h"
12 #include "chrome/browser/extensions/extension_tab_helper.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/tabs/tab_strip_model.h"
15 #include "chrome/browser/themes/theme_service.h"
16 #include "chrome/browser/themes/theme_service_factory.h"
17 #include "chrome/browser/ui/gtk/gtk_util.h"
18 #include "chrome/browser/ui/gtk/tabs/tab_renderer_gtk.h"
19 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
20 #include "content/browser/renderer_host/backing_store_x.h"
21 #include "content/browser/renderer_host/render_view_host.h"
22 #include "content/browser/tab_contents/tab_contents.h"
23 #include "third_party/skia/include/core/SkShader.h"
24 #include "ui/base/x/x11_util.h"
25 #include "ui/gfx/gtk_util.h"
26
27 namespace {
28
29 // The size of the dragged window frame.
30 const int kDragFrameBorderSize = 1;
31 const int kTwiceDragFrameBorderSize = 2 * kDragFrameBorderSize;
32
33 // Used to scale the dragged window sizes.
34 const float kScalingFactor = 0.5;
35
36 const int kAnimateToBoundsDurationMs = 150;
37
38 const gdouble kTransparentAlpha = (200.0f / 255.0f);
39 const gdouble kOpaqueAlpha = 1.0f;
40 const double kDraggedTabBorderColor[] = { 103.0 / 0xff,
41 129.0 / 0xff,
42 162.0 / 0xff };
43
44 } // namespace
45
46 ////////////////////////////////////////////////////////////////////////////////
47 // DraggedTabGtk, public:
48
DraggedTabGtk(TabContents * datasource,const gfx::Point & mouse_tab_offset,const gfx::Size & contents_size,bool mini)49 DraggedTabGtk::DraggedTabGtk(TabContents* datasource,
50 const gfx::Point& mouse_tab_offset,
51 const gfx::Size& contents_size,
52 bool mini)
53 : data_source_(datasource),
54 renderer_(new TabRendererGtk(ThemeServiceFactory::GetForProfile(
55 datasource->profile()))),
56 attached_(false),
57 mouse_tab_offset_(mouse_tab_offset),
58 attached_tab_size_(TabRendererGtk::GetMinimumSelectedSize()),
59 contents_size_(contents_size),
60 close_animation_(this) {
61 TabContentsWrapper* wrapper =
62 TabContentsWrapper::GetCurrentWrapperForContents(datasource);
63 renderer_->UpdateData(datasource,
64 wrapper->extension_tab_helper()->is_app(),
65 false); // loading_only
66 renderer_->set_mini(mini);
67
68 container_ = gtk_window_new(GTK_WINDOW_POPUP);
69 SetContainerColorMap();
70 gtk_widget_set_app_paintable(container_, TRUE);
71 g_signal_connect(container_, "expose-event",
72 G_CALLBACK(OnExposeEvent), this);
73 gtk_widget_add_events(container_, GDK_STRUCTURE_MASK);
74
75 // We contain the tab renderer in a GtkFixed in order to maintain the
76 // requested size. Otherwise, the widget will fill the entire window and
77 // cause a crash when rendering because the bounds don't match our images.
78 fixed_ = gtk_fixed_new();
79 gtk_fixed_put(GTK_FIXED(fixed_), renderer_->widget(), 0, 0);
80 gtk_container_add(GTK_CONTAINER(container_), fixed_);
81 gtk_widget_show_all(container_);
82 }
83
~DraggedTabGtk()84 DraggedTabGtk::~DraggedTabGtk() {
85 gtk_widget_destroy(container_);
86 }
87
MoveTo(const gfx::Point & screen_point)88 void DraggedTabGtk::MoveTo(const gfx::Point& screen_point) {
89 int x = screen_point.x() + mouse_tab_offset_.x() -
90 ScaleValue(mouse_tab_offset_.x());
91 int y = screen_point.y() + mouse_tab_offset_.y() -
92 ScaleValue(mouse_tab_offset_.y());
93
94 gtk_window_move(GTK_WINDOW(container_), x, y);
95 }
96
Attach(int selected_width)97 void DraggedTabGtk::Attach(int selected_width) {
98 attached_ = true;
99 Resize(selected_width);
100
101 if (gtk_util::IsScreenComposited())
102 gdk_window_set_opacity(container_->window, kOpaqueAlpha);
103 }
104
Resize(int width)105 void DraggedTabGtk::Resize(int width) {
106 attached_tab_size_.set_width(width);
107 ResizeContainer();
108 }
109
Detach()110 void DraggedTabGtk::Detach() {
111 attached_ = false;
112 ResizeContainer();
113
114 if (gtk_util::IsScreenComposited())
115 gdk_window_set_opacity(container_->window, kTransparentAlpha);
116 }
117
Update()118 void DraggedTabGtk::Update() {
119 gtk_widget_queue_draw(container_);
120 }
121
AnimateToBounds(const gfx::Rect & bounds,AnimateToBoundsCallback * callback)122 void DraggedTabGtk::AnimateToBounds(const gfx::Rect& bounds,
123 AnimateToBoundsCallback* callback) {
124 animation_callback_.reset(callback);
125
126 gint x, y, width, height;
127 gdk_window_get_origin(container_->window, &x, &y);
128 gdk_window_get_geometry(container_->window, NULL, NULL,
129 &width, &height, NULL);
130
131 animation_start_bounds_ = gfx::Rect(x, y, width, height);
132 animation_end_bounds_ = bounds;
133
134 close_animation_.SetSlideDuration(kAnimateToBoundsDurationMs);
135 close_animation_.SetTweenType(ui::Tween::EASE_OUT);
136 if (!close_animation_.IsShowing()) {
137 close_animation_.Reset();
138 close_animation_.Show();
139 }
140 }
141
142 ////////////////////////////////////////////////////////////////////////////////
143 // DraggedTabGtk, ui::AnimationDelegate implementation:
144
AnimationProgressed(const ui::Animation * animation)145 void DraggedTabGtk::AnimationProgressed(const ui::Animation* animation) {
146 int delta_x = (animation_end_bounds_.x() - animation_start_bounds_.x());
147 int x = animation_start_bounds_.x() +
148 static_cast<int>(delta_x * animation->GetCurrentValue());
149 int y = animation_end_bounds_.y();
150 gdk_window_move(container_->window, x, y);
151 }
152
AnimationEnded(const ui::Animation * animation)153 void DraggedTabGtk::AnimationEnded(const ui::Animation* animation) {
154 animation_callback_->Run();
155 }
156
AnimationCanceled(const ui::Animation * animation)157 void DraggedTabGtk::AnimationCanceled(const ui::Animation* animation) {
158 AnimationEnded(animation);
159 }
160
161 ////////////////////////////////////////////////////////////////////////////////
162 // DraggedTabGtk, private:
163
Layout()164 void DraggedTabGtk::Layout() {
165 if (attached_) {
166 renderer_->SetBounds(gfx::Rect(GetPreferredSize()));
167 } else {
168 int left = 0;
169 if (base::i18n::IsRTL())
170 left = GetPreferredSize().width() - attached_tab_size_.width();
171
172 // The renderer_'s width should be attached_tab_size_.width() in both LTR
173 // and RTL locales. Wrong width will cause the wrong positioning of the tab
174 // view in dragging. Please refer to http://crbug.com/6223 for details.
175 renderer_->SetBounds(gfx::Rect(left, 0, attached_tab_size_.width(),
176 attached_tab_size_.height()));
177 }
178 }
179
GetPreferredSize()180 gfx::Size DraggedTabGtk::GetPreferredSize() {
181 if (attached_)
182 return attached_tab_size_;
183
184 int width = std::max(attached_tab_size_.width(), contents_size_.width()) +
185 kTwiceDragFrameBorderSize;
186 int height = attached_tab_size_.height() + kDragFrameBorderSize +
187 contents_size_.height();
188 return gfx::Size(width, height);
189 }
190
ResizeContainer()191 void DraggedTabGtk::ResizeContainer() {
192 gfx::Size size = GetPreferredSize();
193 gtk_window_resize(GTK_WINDOW(container_),
194 ScaleValue(size.width()), ScaleValue(size.height()));
195 Layout();
196 }
197
ScaleValue(int value)198 int DraggedTabGtk::ScaleValue(int value) {
199 return attached_ ? value : static_cast<int>(value * kScalingFactor);
200 }
201
bounds() const202 gfx::Rect DraggedTabGtk::bounds() const {
203 gint x, y, width, height;
204 gtk_window_get_position(GTK_WINDOW(container_), &x, &y);
205 gtk_window_get_size(GTK_WINDOW(container_), &width, &height);
206 return gfx::Rect(x, y, width, height);
207 }
208
SetContainerColorMap()209 void DraggedTabGtk::SetContainerColorMap() {
210 GdkScreen* screen = gtk_widget_get_screen(container_);
211 GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen);
212
213 // If rgba is not available, use rgb instead.
214 if (!colormap)
215 colormap = gdk_screen_get_rgb_colormap(screen);
216
217 gtk_widget_set_colormap(container_, colormap);
218 }
219
SetContainerTransparency()220 void DraggedTabGtk::SetContainerTransparency() {
221 cairo_t* cairo_context = gdk_cairo_create(container_->window);
222 if (!cairo_context)
223 return;
224
225 // Make the background of the dragged tab window fully transparent. All of
226 // the content of the window (child widgets) will be completely opaque.
227 gfx::Size size = bounds().size();
228 cairo_scale(cairo_context, static_cast<double>(size.width()),
229 static_cast<double>(size.height()));
230 cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f);
231 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
232 cairo_paint(cairo_context);
233 cairo_destroy(cairo_context);
234 }
235
SetContainerShapeMask(cairo_surface_t * surface)236 void DraggedTabGtk::SetContainerShapeMask(cairo_surface_t* surface) {
237 // Create a 1bpp bitmap the size of |container_|.
238 gfx::Size size = bounds().size();
239 GdkPixmap* pixmap = gdk_pixmap_new(NULL, size.width(), size.height(), 1);
240 cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap));
241
242 // Set the transparency.
243 cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f);
244
245 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will
246 // be opaque in the container window.
247 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
248 if (!attached_)
249 cairo_scale(cairo_context, kScalingFactor, kScalingFactor);
250 cairo_set_source_surface(cairo_context, surface, 0, 0);
251 cairo_paint(cairo_context);
252
253 if (!attached_) {
254 // Make the render area depiction opaque (leaving enough room for the
255 // border).
256 cairo_identity_matrix(cairo_context);
257 // On Lucid running VNC, the X server will reject RGBA (1,1,1,1) as an
258 // invalid value below in gdk_window_shape_combine_mask(). Using (0,0,0,1)
259 // instead. The value doesn't really matter, as long as the alpha is not 0.
260 cairo_set_source_rgba(cairo_context, 0.0f, 0.0f, 0.0f, 1.0f);
261 int tab_height = static_cast<int>(kScalingFactor *
262 renderer_->height() -
263 kDragFrameBorderSize);
264 cairo_rectangle(cairo_context,
265 0, tab_height,
266 size.width(), size.height() - tab_height);
267 cairo_fill(cairo_context);
268 }
269
270 cairo_destroy(cairo_context);
271
272 // Set the shape mask.
273 gdk_window_shape_combine_mask(container_->window, pixmap, 0, 0);
274 g_object_unref(pixmap);
275 }
276
277 // static
OnExposeEvent(GtkWidget * widget,GdkEventExpose * event,DraggedTabGtk * dragged_tab)278 gboolean DraggedTabGtk::OnExposeEvent(GtkWidget* widget,
279 GdkEventExpose* event,
280 DraggedTabGtk* dragged_tab) {
281 cairo_surface_t* surface = dragged_tab->renderer_->PaintToSurface();
282 if (gtk_util::IsScreenComposited()) {
283 dragged_tab->SetContainerTransparency();
284 } else {
285 dragged_tab->SetContainerShapeMask(surface);
286 }
287
288 // Only used when not attached.
289 int tab_width = static_cast<int>(kScalingFactor *
290 dragged_tab->renderer_->width());
291 int tab_height = static_cast<int>(kScalingFactor *
292 dragged_tab->renderer_->height());
293
294 // Draw the render area.
295 BackingStore* backing_store =
296 dragged_tab->data_source_->render_view_host()->GetBackingStore(false);
297 if (backing_store && !dragged_tab->attached_) {
298 // This leaves room for the border.
299 static_cast<BackingStoreX*>(backing_store)->PaintToRect(
300 gfx::Rect(kDragFrameBorderSize, tab_height,
301 widget->allocation.width - kTwiceDragFrameBorderSize,
302 widget->allocation.height - tab_height -
303 kDragFrameBorderSize),
304 GDK_DRAWABLE(widget->window));
305 }
306
307 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
308 // Draw the border.
309 if (!dragged_tab->attached_) {
310 cairo_set_line_width(cr, kDragFrameBorderSize);
311 cairo_set_source_rgb(cr, kDraggedTabBorderColor[0],
312 kDraggedTabBorderColor[1],
313 kDraggedTabBorderColor[2]);
314 // |offset| is the distance from the edge of the image to the middle of
315 // the border line.
316 double offset = kDragFrameBorderSize / 2.0 - 0.5;
317 double left_x = offset;
318 double top_y = tab_height - kDragFrameBorderSize + offset;
319 double right_x = widget->allocation.width - offset;
320 double bottom_y = widget->allocation.height - offset;
321 double middle_x = tab_width + offset;
322
323 // We don't use cairo_rectangle() because we don't want to draw the border
324 // under the tab itself.
325 cairo_move_to(cr, left_x, top_y);
326 cairo_line_to(cr, left_x, bottom_y);
327 cairo_line_to(cr, right_x, bottom_y);
328 cairo_line_to(cr, right_x, top_y);
329 cairo_line_to(cr, middle_x, top_y);
330 cairo_stroke(cr);
331 }
332
333 // Draw the tab.
334 if (!dragged_tab->attached_)
335 cairo_scale(cr, kScalingFactor, kScalingFactor);
336 cairo_set_source_surface(cr, surface, 0, 0);
337 cairo_paint(cr);
338
339 cairo_destroy(cr);
340
341 cairo_surface_destroy(surface);
342
343 // We've already drawn the tab, so don't propagate the expose-event signal.
344 return TRUE;
345 }
346