OLD | NEW |
| (Empty) |
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(gfx::Tween::EASE_OUT); | |
220 if (!close_animation_.IsShowing()) { | |
221 close_animation_.Reset(); | |
222 close_animation_.Show(); | |
223 } | |
224 } | |
225 | |
226 //////////////////////////////////////////////////////////////////////////////// | |
227 // DraggedViewGtk, gfx::AnimationDelegate implementation: | |
228 | |
229 void DraggedViewGtk::AnimationProgressed(const gfx::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 gfx::Animation* animation) { | |
239 animation_callback_.Run(); | |
240 } | |
241 | |
242 void DraggedViewGtk::AnimationCanceled(const gfx::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 } | |
OLD | NEW |