Home | History | Annotate | Download | only in tabs
      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