• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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