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/dragged_view_gtk.h" 6 7 #include <gdk/gdk.h> 8 9 #include <algorithm> 10 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" 29 30 using content::WebContents; 31 32 namespace { 33 34 // The size of the dragged window frame. 35 const int kDragFrameBorderSize = 1; 36 const int kTwiceDragFrameBorderSize = 2 * kDragFrameBorderSize; 37 38 // Used to scale the dragged window sizes. 39 const float kScalingFactor = 0.5; 40 41 const int kAnimateToBoundsDurationMs = 150; 42 43 const gdouble kTransparentAlpha = (200.0f / 255.0f); 44 const gdouble kOpaqueAlpha = 1.0f; 45 const double kDraggedTabBorderColor[] = { 103.0 / 0xff, 46 129.0 / 0xff, 47 162.0 / 0xff }; 48 49 } // namespace 50 51 //////////////////////////////////////////////////////////////////////////////// 52 // DraggedViewGtk, public: 53 54 DraggedViewGtk::DraggedViewGtk(DragData* drag_data, 55 const gfx::Point& mouse_tab_offset, 56 const gfx::Size& contents_size) 57 : drag_data_(drag_data), 58 mini_width_(-1), 59 normal_width_(-1), 60 attached_(false), 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())))); 70 } 71 72 for (size_t i = 0; i < drag_data_->size(); i++) { 73 WebContents* web_contents = drag_data_->get(i)->contents_; 74 renderers_[i]->UpdateData( 75 web_contents, 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()); 80 } 81 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); 87 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); 94 95 gtk_container_add(GTK_CONTAINER(container_), fixed_); 96 gtk_widget_show_all(container_); 97 } 98 99 DraggedViewGtk::~DraggedViewGtk() { 100 gtk_widget_destroy(container_); 101 STLDeleteElements(&renderers_); 102 } 103 104 void DraggedViewGtk::MoveDetachedTo(const gfx::Point& screen_point) { 105 DCHECK(!attached_); 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); 111 } 112 113 void DraggedViewGtk::MoveAttachedTo(const gfx::Point& tabstrip_point) { 114 DCHECK(attached_); 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); 120 } 121 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); 127 return start_point; 128 } 129 130 void DraggedViewGtk::Attach( 131 int normal_width, int mini_width, int window_width) { 132 attached_ = true; 133 parent_window_width_ = window_width; 134 normal_width_ = normal_width; 135 mini_width_ = mini_width; 136 137 int dragged_tab_width = 138 drag_data_->GetSourceTabData()->mini_ ? mini_width : normal_width; 139 140 Resize(dragged_tab_width); 141 142 if (ui::IsScreenComposited()) { 143 GdkWindow* gdk_window = gtk_widget_get_window(container_); 144 gdk_window_set_opacity(gdk_window, kOpaqueAlpha); 145 } 146 } 147 148 void DraggedViewGtk::Resize(int width) { 149 attached_tab_size_.set_width(width); 150 ResizeContainer(); 151 } 152 153 void DraggedViewGtk::Detach() { 154 attached_ = false; 155 ResizeContainer(); 156 157 if (ui::IsScreenComposited()) { 158 GdkWindow* gdk_window = gtk_widget_get_window(container_); 159 gdk_window_set_opacity(gdk_window, kTransparentAlpha); 160 } 161 } 162 163 void DraggedViewGtk::Update() { 164 gtk_widget_queue_draw(container_); 165 } 166 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())); 170 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; 177 return width; 178 } 179 180 int DraggedViewGtk::GetTotalWidthInTabStrip() { 181 return GetWidthInTabStripFromTo(0, drag_data_->size()); 182 } 183 184 int DraggedViewGtk::GetWidthInTabStripUpToSourceTab() { 185 if (!base::i18n::IsRTL()) { 186 return GetWidthInTabStripFromTo(0, drag_data_->source_tab_index()); 187 } else { 188 return GetWidthInTabStripFromTo( 189 drag_data_->source_tab_index() + 1, drag_data_->size()); 190 } 191 } 192 193 int DraggedViewGtk::GetWidthInTabStripUpToMousePointer() { 194 int width = GetWidthInTabStripUpToSourceTab() + mouse_tab_offset_.x(); 195 if (!base::i18n::IsRTL() && drag_data_->source_tab_index() > 0) { 196 width -= 16; 197 } else if (base::i18n::IsRTL() && 198 drag_data_->source_tab_index() < 199 static_cast<int>(drag_data_->size()) - 1) { 200 width -= 16; 201 } 202 return width; 203 } 204 205 void DraggedViewGtk::AnimateToBounds(const gfx::Rect& bounds, 206 const base::Closure& callback) { 207 animation_callback_ = callback; 208 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); 214 215 animation_start_bounds_ = gfx::Rect(x, y, width, height); 216 animation_end_bounds_ = bounds; 217 218 close_animation_.SetSlideDuration(kAnimateToBoundsDurationMs); 219 close_animation_.SetTweenType(ui::Tween::EASE_OUT); 220 if (!close_animation_.IsShowing()) { 221 close_animation_.Reset(); 222 close_animation_.Show(); 223 } 224 } 225 226 //////////////////////////////////////////////////////////////////////////////// 227 // DraggedViewGtk, ui::AnimationDelegate implementation: 228 229 void DraggedViewGtk::AnimationProgressed(const ui::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); 236 } 237 238 void DraggedViewGtk::AnimationEnded(const ui::Animation* animation) { 239 animation_callback_.Run(); 240 } 241 242 void DraggedViewGtk::AnimationCanceled(const ui::Animation* animation) { 243 AnimationEnded(animation); 244 } 245 246 //////////////////////////////////////////////////////////////////////////////// 247 // DraggedViewGtk, private: 248 249 void DraggedViewGtk::Layout() { 250 if (attached_) { 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); 255 } 256 } else { 257 int left = 0; 258 if (base::i18n::IsRTL()) 259 left = GetPreferredSize().width() - attached_tab_size_.width(); 260 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())); 267 } 268 } 269 270 gfx::Size DraggedViewGtk::GetPreferredSize() { 271 if (attached_) { 272 gfx::Size preferred_size(attached_tab_size_); 273 preferred_size.set_width(GetTotalWidthInTabStrip()); 274 return preferred_size; 275 } 276 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); 282 } 283 284 void DraggedViewGtk::ResizeContainer() { 285 gfx::Size size = GetPreferredSize(); 286 gtk_window_resize(GTK_WINDOW(container_), 287 ScaleValue(size.width()), ScaleValue(size.height())); 288 Layout(); 289 } 290 291 int DraggedViewGtk::ScaleValue(int value) { 292 return attached_ ? value : static_cast<int>(value * kScalingFactor); 293 } 294 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); 300 } 301 302 int DraggedViewGtk::GetAttachedTabWidthAt(int index) { 303 return drag_data_->get(index)->mini_? mini_width_ : normal_width_; 304 } 305 306 void DraggedViewGtk::SetContainerColorMap() { 307 GdkScreen* screen = gtk_widget_get_screen(container_); 308 GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); 309 310 // If rgba is not available, use rgb instead. 311 if (!colormap) 312 colormap = gdk_screen_get_rgb_colormap(screen); 313 314 gtk_widget_set_colormap(container_, colormap); 315 } 316 317 void DraggedViewGtk::SetContainerTransparency() { 318 cairo_t* cairo_context = gdk_cairo_create(gtk_widget_get_window(container_)); 319 if (!cairo_context) 320 return; 321 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); 331 } 332 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)); 338 339 // Set the transparency. 340 cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); 341 342 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will 343 // be opaque in the container window. 344 if (!attached_) 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); 349 else 350 cairo_set_operator(cairo_context, CAIRO_OPERATOR_OVER); 351 352 GtkAllocation allocation; 353 gtk_widget_get_allocation(container_, &allocation); 354 PaintTab(i, container_, cairo_context, allocation.width); 355 } 356 357 if (!attached_) { 358 // Make the render area depiction opaque (leaving enough room for the 359 // border). 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, 369 0, tab_height, 370 size.width(), size.height() - tab_height); 371 cairo_fill(cairo_context); 372 } 373 374 cairo_destroy(cairo_context); 375 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); 380 } 381 382 gboolean DraggedViewGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) { 383 TRACE_EVENT0("ui::gtk", "DraggedViewGtk::OnExpose"); 384 385 if (ui::IsScreenComposited()) 386 SetContainerTransparency(); 387 else 388 SetContainerShapeMask(); 389 390 // Only used when not attached. 391 int tab_height = static_cast<int>( 392 kScalingFactor * renderers_[drag_data_->source_tab_index()]->height()); 393 394 GtkAllocation allocation; 395 gtk_widget_get_allocation(widget, &allocation); 396 397 // Draw the render area. 398 if (!attached_) { 399 content::RenderWidgetHost* render_widget_host = 400 drag_data_->GetSourceWebContents()->GetRenderViewHost(); 401 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))); 409 } 410 411 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); 412 // Draw the border. 413 if (!attached_) { 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 419 // the border line. 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; 425 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); 431 cairo_stroke(cr); 432 } 433 434 // Draw the tab. 435 if (!attached_) 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()) 440 continue; 441 PaintTab(i, widget, cr, allocation.width); 442 } 443 // Painting the active tab last, so that it appears on top. 444 PaintTab(drag_data_->source_tab_index(), widget, cr, 445 allocation.width); 446 447 cairo_destroy(cr); 448 449 // We've already drawn the tab, so don't propagate the expose-event signal. 450 return TRUE; 451 } 452 453 void DraggedViewGtk::PaintTab(int index, GtkWidget* widget, cairo_t* cr, 454 int widget_width) { 455 renderers_[index]->set_mini(drag_data_->get(index)->mini_); 456 cairo_surface_t* surface = renderers_[index]->PaintToSurface(widget, cr); 457 458 int paint_at = 0; 459 if (!base::i18n::IsRTL()) { 460 paint_at = std::max(GetWidthInTabStripFromTo(0, index) - 16, 0); 461 } else { 462 paint_at = GetTotalWidthInTabStrip() - 463 GetWidthInTabStripFromTo(0, index + 1); 464 if (!attached_) { 465 paint_at = widget_width / kScalingFactor - 466 GetWidthInTabStripFromTo(0, index + 1); 467 } 468 } 469 470 cairo_set_source_surface(cr, surface, paint_at, 0); 471 cairo_paint(cr); 472 cairo_surface_destroy(surface); 473 } 474