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