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 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 84 DraggedTabGtk::~DraggedTabGtk() { 85 gtk_widget_destroy(container_); 86 } 87 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 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 105 void DraggedTabGtk::Resize(int width) { 106 attached_tab_size_.set_width(width); 107 ResizeContainer(); 108 } 109 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 118 void DraggedTabGtk::Update() { 119 gtk_widget_queue_draw(container_); 120 } 121 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 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 153 void DraggedTabGtk::AnimationEnded(const ui::Animation* animation) { 154 animation_callback_->Run(); 155 } 156 157 void DraggedTabGtk::AnimationCanceled(const ui::Animation* animation) { 158 AnimationEnded(animation); 159 } 160 161 //////////////////////////////////////////////////////////////////////////////// 162 // DraggedTabGtk, private: 163 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 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 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 198 int DraggedTabGtk::ScaleValue(int value) { 199 return attached_ ? value : static_cast<int>(value * kScalingFactor); 200 } 201 202 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 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 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 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 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