| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/gtk/tabs/dragged_tab_gtk.h" |
| |
| #include <gdk/gdk.h> |
| |
| #include <algorithm> |
| |
| #include "app/x11_util.h" |
| #include "base/i18n/rtl.h" |
| #include "chrome/browser/gtk/gtk_util.h" |
| #include "chrome/browser/gtk/tabs/tab_renderer_gtk.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/renderer_host/backing_store_x.h" |
| #include "chrome/browser/renderer_host/render_view_host.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #include "chrome/browser/tabs/tab_strip_model.h" |
| #include "chrome/browser/themes/browser_theme_provider.h" |
| #include "gfx/gtk_util.h" |
| #include "third_party/skia/include/core/SkShader.h" |
| |
| namespace { |
| |
| // The size of the dragged window frame. |
| const int kDragFrameBorderSize = 1; |
| const int kTwiceDragFrameBorderSize = 2 * kDragFrameBorderSize; |
| |
| // Used to scale the dragged window sizes. |
| const float kScalingFactor = 0.5; |
| |
| const int kAnimateToBoundsDurationMs = 150; |
| |
| const gdouble kTransparentAlpha = (200.0f / 255.0f); |
| const gdouble kOpaqueAlpha = 1.0f; |
| const double kDraggedTabBorderColor[] = { 103.0 / 0xff, |
| 129.0 / 0xff, |
| 162.0 / 0xff }; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabGtk, public: |
| |
| DraggedTabGtk::DraggedTabGtk(TabContents* datasource, |
| const gfx::Point& mouse_tab_offset, |
| const gfx::Size& contents_size, |
| bool mini) |
| : data_source_(datasource), |
| renderer_(new TabRendererGtk(datasource->profile()->GetThemeProvider())), |
| attached_(false), |
| mouse_tab_offset_(mouse_tab_offset), |
| attached_tab_size_(TabRendererGtk::GetMinimumSelectedSize()), |
| contents_size_(contents_size), |
| close_animation_(this) { |
| renderer_->UpdateData(datasource, |
| datasource->is_app(), |
| false); // loading_only |
| renderer_->set_mini(mini); |
| |
| container_ = gtk_window_new(GTK_WINDOW_POPUP); |
| SetContainerColorMap(); |
| gtk_widget_set_app_paintable(container_, TRUE); |
| g_signal_connect(container_, "expose-event", |
| G_CALLBACK(OnExposeEvent), this); |
| gtk_widget_add_events(container_, GDK_STRUCTURE_MASK); |
| |
| // We contain the tab renderer in a GtkFixed in order to maintain the |
| // requested size. Otherwise, the widget will fill the entire window and |
| // cause a crash when rendering because the bounds don't match our images. |
| fixed_ = gtk_fixed_new(); |
| gtk_fixed_put(GTK_FIXED(fixed_), renderer_->widget(), 0, 0); |
| gtk_container_add(GTK_CONTAINER(container_), fixed_); |
| gtk_widget_show_all(container_); |
| } |
| |
| DraggedTabGtk::~DraggedTabGtk() { |
| gtk_widget_destroy(container_); |
| } |
| |
| void DraggedTabGtk::MoveTo(const gfx::Point& screen_point) { |
| int x = screen_point.x() + mouse_tab_offset_.x() - |
| ScaleValue(mouse_tab_offset_.x()); |
| int y = screen_point.y() + mouse_tab_offset_.y() - |
| ScaleValue(mouse_tab_offset_.y()); |
| |
| gtk_window_move(GTK_WINDOW(container_), x, y); |
| } |
| |
| void DraggedTabGtk::Attach(int selected_width) { |
| attached_ = true; |
| Resize(selected_width); |
| |
| if (gtk_util::IsScreenComposited()) |
| gdk_window_set_opacity(container_->window, kOpaqueAlpha); |
| } |
| |
| void DraggedTabGtk::Resize(int width) { |
| attached_tab_size_.set_width(width); |
| ResizeContainer(); |
| } |
| |
| void DraggedTabGtk::Detach() { |
| attached_ = false; |
| ResizeContainer(); |
| |
| if (gtk_util::IsScreenComposited()) |
| gdk_window_set_opacity(container_->window, kTransparentAlpha); |
| } |
| |
| void DraggedTabGtk::Update() { |
| gtk_widget_queue_draw(container_); |
| } |
| |
| void DraggedTabGtk::AnimateToBounds(const gfx::Rect& bounds, |
| AnimateToBoundsCallback* callback) { |
| animation_callback_.reset(callback); |
| |
| gint x, y, width, height; |
| gdk_window_get_origin(container_->window, &x, &y); |
| gdk_window_get_geometry(container_->window, NULL, NULL, |
| &width, &height, NULL); |
| |
| animation_start_bounds_ = gfx::Rect(x, y, width, height); |
| animation_end_bounds_ = bounds; |
| |
| close_animation_.SetSlideDuration(kAnimateToBoundsDurationMs); |
| close_animation_.SetTweenType(ui::Tween::EASE_OUT); |
| if (!close_animation_.IsShowing()) { |
| close_animation_.Reset(); |
| close_animation_.Show(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabGtk, ui::AnimationDelegate implementation: |
| |
| void DraggedTabGtk::AnimationProgressed(const ui::Animation* animation) { |
| int delta_x = (animation_end_bounds_.x() - animation_start_bounds_.x()); |
| int x = animation_start_bounds_.x() + |
| static_cast<int>(delta_x * animation->GetCurrentValue()); |
| int y = animation_end_bounds_.y(); |
| gdk_window_move(container_->window, x, y); |
| } |
| |
| void DraggedTabGtk::AnimationEnded(const ui::Animation* animation) { |
| animation_callback_->Run(); |
| } |
| |
| void DraggedTabGtk::AnimationCanceled(const ui::Animation* animation) { |
| AnimationEnded(animation); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabGtk, private: |
| |
| void DraggedTabGtk::Layout() { |
| if (attached_) { |
| renderer_->SetBounds(gfx::Rect(GetPreferredSize())); |
| } else { |
| int left = 0; |
| if (base::i18n::IsRTL()) |
| left = GetPreferredSize().width() - attached_tab_size_.width(); |
| |
| // The renderer_'s width should be attached_tab_size_.width() in both LTR |
| // and RTL locales. Wrong width will cause the wrong positioning of the tab |
| // view in dragging. Please refer to http://crbug.com/6223 for details. |
| renderer_->SetBounds(gfx::Rect(left, 0, attached_tab_size_.width(), |
| attached_tab_size_.height())); |
| } |
| } |
| |
| gfx::Size DraggedTabGtk::GetPreferredSize() { |
| if (attached_) |
| return attached_tab_size_; |
| |
| int width = std::max(attached_tab_size_.width(), contents_size_.width()) + |
| kTwiceDragFrameBorderSize; |
| int height = attached_tab_size_.height() + kDragFrameBorderSize + |
| contents_size_.height(); |
| return gfx::Size(width, height); |
| } |
| |
| void DraggedTabGtk::ResizeContainer() { |
| gfx::Size size = GetPreferredSize(); |
| gtk_window_resize(GTK_WINDOW(container_), |
| ScaleValue(size.width()), ScaleValue(size.height())); |
| Layout(); |
| } |
| |
| int DraggedTabGtk::ScaleValue(int value) { |
| return attached_ ? value : static_cast<int>(value * kScalingFactor); |
| } |
| |
| gfx::Rect DraggedTabGtk::bounds() const { |
| gint x, y, width, height; |
| gtk_window_get_position(GTK_WINDOW(container_), &x, &y); |
| gtk_window_get_size(GTK_WINDOW(container_), &width, &height); |
| return gfx::Rect(x, y, width, height); |
| } |
| |
| void DraggedTabGtk::SetContainerColorMap() { |
| GdkScreen* screen = gtk_widget_get_screen(container_); |
| GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); |
| |
| // If rgba is not available, use rgb instead. |
| if (!colormap) |
| colormap = gdk_screen_get_rgb_colormap(screen); |
| |
| gtk_widget_set_colormap(container_, colormap); |
| } |
| |
| void DraggedTabGtk::SetContainerTransparency() { |
| cairo_t* cairo_context = gdk_cairo_create(container_->window); |
| if (!cairo_context) |
| return; |
| |
| // Make the background of the dragged tab window fully transparent. All of |
| // the content of the window (child widgets) will be completely opaque. |
| gfx::Size size = bounds().size(); |
| cairo_scale(cairo_context, static_cast<double>(size.width()), |
| static_cast<double>(size.height())); |
| cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); |
| cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| cairo_paint(cairo_context); |
| cairo_destroy(cairo_context); |
| } |
| |
| void DraggedTabGtk::SetContainerShapeMask(cairo_surface_t* surface) { |
| // Create a 1bpp bitmap the size of |container_|. |
| gfx::Size size = bounds().size(); |
| GdkPixmap* pixmap = gdk_pixmap_new(NULL, size.width(), size.height(), 1); |
| cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap)); |
| |
| // Set the transparency. |
| cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); |
| |
| // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will |
| // be opaque in the container window. |
| cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| if (!attached_) |
| cairo_scale(cairo_context, kScalingFactor, kScalingFactor); |
| cairo_set_source_surface(cairo_context, surface, 0, 0); |
| cairo_paint(cairo_context); |
| |
| if (!attached_) { |
| // Make the render area depiction opaque (leaving enough room for the |
| // border). |
| cairo_identity_matrix(cairo_context); |
| // On Lucid running VNC, the X server will reject RGBA (1,1,1,1) as an |
| // invalid value below in gdk_window_shape_combine_mask(). Using (0,0,0,1) |
| // instead. The value doesn't really matter, as long as the alpha is not 0. |
| cairo_set_source_rgba(cairo_context, 0.0f, 0.0f, 0.0f, 1.0f); |
| int tab_height = static_cast<int>(kScalingFactor * |
| renderer_->height() - |
| kDragFrameBorderSize); |
| cairo_rectangle(cairo_context, |
| 0, tab_height, |
| size.width(), size.height() - tab_height); |
| cairo_fill(cairo_context); |
| } |
| |
| cairo_destroy(cairo_context); |
| |
| // Set the shape mask. |
| gdk_window_shape_combine_mask(container_->window, pixmap, 0, 0); |
| g_object_unref(pixmap); |
| } |
| |
| // static |
| gboolean DraggedTabGtk::OnExposeEvent(GtkWidget* widget, |
| GdkEventExpose* event, |
| DraggedTabGtk* dragged_tab) { |
| cairo_surface_t* surface = dragged_tab->renderer_->PaintToSurface(); |
| if (gtk_util::IsScreenComposited()) { |
| dragged_tab->SetContainerTransparency(); |
| } else { |
| dragged_tab->SetContainerShapeMask(surface); |
| } |
| |
| // Only used when not attached. |
| int tab_width = static_cast<int>(kScalingFactor * |
| dragged_tab->renderer_->width()); |
| int tab_height = static_cast<int>(kScalingFactor * |
| dragged_tab->renderer_->height()); |
| |
| // Draw the render area. |
| BackingStore* backing_store = |
| dragged_tab->data_source_->render_view_host()->GetBackingStore(false); |
| if (backing_store && !dragged_tab->attached_) { |
| // This leaves room for the border. |
| static_cast<BackingStoreX*>(backing_store)->PaintToRect( |
| gfx::Rect(kDragFrameBorderSize, tab_height, |
| widget->allocation.width - kTwiceDragFrameBorderSize, |
| widget->allocation.height - tab_height - |
| kDragFrameBorderSize), |
| GDK_DRAWABLE(widget->window)); |
| } |
| |
| cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window)); |
| // Draw the border. |
| if (!dragged_tab->attached_) { |
| cairo_set_line_width(cr, kDragFrameBorderSize); |
| cairo_set_source_rgb(cr, kDraggedTabBorderColor[0], |
| kDraggedTabBorderColor[1], |
| kDraggedTabBorderColor[2]); |
| // |offset| is the distance from the edge of the image to the middle of |
| // the border line. |
| double offset = kDragFrameBorderSize / 2.0 - 0.5; |
| double left_x = offset; |
| double top_y = tab_height - kDragFrameBorderSize + offset; |
| double right_x = widget->allocation.width - offset; |
| double bottom_y = widget->allocation.height - offset; |
| double middle_x = tab_width + offset; |
| |
| // We don't use cairo_rectangle() because we don't want to draw the border |
| // under the tab itself. |
| cairo_move_to(cr, left_x, top_y); |
| cairo_line_to(cr, left_x, bottom_y); |
| cairo_line_to(cr, right_x, bottom_y); |
| cairo_line_to(cr, right_x, top_y); |
| cairo_line_to(cr, middle_x, top_y); |
| cairo_stroke(cr); |
| } |
| |
| // Draw the tab. |
| if (!dragged_tab->attached_) |
| cairo_scale(cr, kScalingFactor, kScalingFactor); |
| cairo_set_source_surface(cr, surface, 0, 0); |
| cairo_paint(cr); |
| |
| cairo_destroy(cr); |
| |
| cairo_surface_destroy(surface); |
| |
| // We've already drawn the tab, so don't propagate the expose-event signal. |
| return TRUE; |
| } |