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/tab_strip_gtk.h" | |
6 | |
7 #include <gtk/gtk.h> | |
8 | |
9 #include <algorithm> | |
10 #include <string> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/debug/trace_event.h" | |
14 #include "base/i18n/rtl.h" | |
15 #include "base/metrics/histogram.h" | |
16 #include "base/strings/string_util.h" | |
17 #include "base/strings/utf_string_conversions.h" | |
18 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | |
19 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" | |
20 #include "chrome/browser/autocomplete/autocomplete_input.h" | |
21 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
22 #include "chrome/browser/chrome_notification_types.h" | |
23 #include "chrome/browser/profiles/profile.h" | |
24 #include "chrome/browser/themes/theme_properties.h" | |
25 #include "chrome/browser/themes/theme_service.h" | |
26 #include "chrome/browser/ui/browser.h" | |
27 #include "chrome/browser/ui/browser_navigator.h" | |
28 #include "chrome/browser/ui/browser_tabstrip.h" | |
29 #include "chrome/browser/ui/gtk/browser_window_gtk.h" | |
30 #include "chrome/browser/ui/gtk/custom_button.h" | |
31 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
32 #include "chrome/browser/ui/gtk/gtk_util.h" | |
33 #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h" | |
34 #include "chrome/browser/ui/gtk/tabs/tab_strip_menu_controller.h" | |
35 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
36 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" | |
37 #include "content/public/browser/notification_source.h" | |
38 #include "content/public/browser/user_metrics.h" | |
39 #include "content/public/browser/web_contents.h" | |
40 #include "grit/generated_resources.h" | |
41 #include "grit/theme_resources.h" | |
42 #include "grit/ui_resources.h" | |
43 #include "ui/base/dragdrop/gtk_dnd_util.h" | |
44 #include "ui/base/gtk/gtk_screen_util.h" | |
45 #include "ui/base/l10n/l10n_util.h" | |
46 #include "ui/base/resource/resource_bundle.h" | |
47 #include "ui/gfx/animation/animation_delegate.h" | |
48 #include "ui/gfx/animation/slide_animation.h" | |
49 #include "ui/gfx/gtk_compat.h" | |
50 #include "ui/gfx/gtk_util.h" | |
51 #include "ui/gfx/image/image.h" | |
52 #include "ui/gfx/point.h" | |
53 | |
54 using base::UserMetricsAction; | |
55 using content::WebContents; | |
56 | |
57 namespace { | |
58 | |
59 const int kDefaultAnimationDurationMs = 100; | |
60 const int kResizeLayoutAnimationDurationMs = 166; | |
61 const int kReorderAnimationDurationMs = 166; | |
62 const int kMiniTabAnimationDurationMs = 150; | |
63 | |
64 const int kNewTabButtonHOffset = -5; | |
65 const int kNewTabButtonVOffset = 5; | |
66 | |
67 // The delay between when the mouse leaves the tabstrip and the resize animation | |
68 // is started. | |
69 const int kResizeTabsTimeMs = 300; | |
70 | |
71 // A very short time just to make sure we don't clump up our Layout() calls | |
72 // during slow window resizes. | |
73 const int kLayoutAfterSizeAllocateMs = 10; | |
74 | |
75 // The range outside of the tabstrip where the pointer must enter/leave to | |
76 // start/stop the resize animation. | |
77 const int kTabStripAnimationVSlop = 40; | |
78 | |
79 // The horizontal offset from one tab to the next, which results in overlapping | |
80 // tabs. | |
81 const int kTabHOffset = -16; | |
82 | |
83 // Inverse ratio of the width of a tab edge to the width of the tab. When | |
84 // hovering over the left or right edge of a tab, the drop indicator will | |
85 // point between tabs. | |
86 const int kTabEdgeRatioInverse = 4; | |
87 | |
88 // Size of the drop indicator. | |
89 static int drop_indicator_width; | |
90 static int drop_indicator_height; | |
91 | |
92 inline int Round(double x) { | |
93 return static_cast<int>(x + 0.5); | |
94 } | |
95 | |
96 // widget->allocation is not guaranteed to be set. After window creation, | |
97 // we pick up the normal bounds by connecting to the configure-event signal. | |
98 gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) { | |
99 GtkRequisition request; | |
100 gtk_widget_size_request(widget, &request); | |
101 return gfx::Rect(0, 0, request.width, request.height); | |
102 } | |
103 | |
104 // Sort rectangles based on their x position. We don't care about y position | |
105 // so we don't bother breaking ties. | |
106 int CompareGdkRectangles(const void* p1, const void* p2) { | |
107 int p1_x = static_cast<const GdkRectangle*>(p1)->x; | |
108 int p2_x = static_cast<const GdkRectangle*>(p2)->x; | |
109 if (p1_x < p2_x) | |
110 return -1; | |
111 else if (p1_x == p2_x) | |
112 return 0; | |
113 return 1; | |
114 } | |
115 | |
116 bool GdkRectMatchesTabFaviconBounds(const GdkRectangle& gdk_rect, TabGtk* tab) { | |
117 gfx::Rect favicon_bounds = tab->favicon_bounds(); | |
118 return gdk_rect.x == favicon_bounds.x() + tab->x() && | |
119 gdk_rect.y == favicon_bounds.y() + tab->y() && | |
120 gdk_rect.width == favicon_bounds.width() && | |
121 gdk_rect.height == favicon_bounds.height(); | |
122 } | |
123 | |
124 } // namespace | |
125 | |
126 //////////////////////////////////////////////////////////////////////////////// | |
127 // | |
128 // TabAnimation | |
129 // | |
130 // A base class for all TabStrip animations. | |
131 // | |
132 class TabStripGtk::TabAnimation : public gfx::AnimationDelegate { | |
133 public: | |
134 friend class TabStripGtk; | |
135 | |
136 // Possible types of animation. | |
137 enum Type { | |
138 INSERT, | |
139 REMOVE, | |
140 MOVE, | |
141 RESIZE, | |
142 MINI, | |
143 MINI_MOVE | |
144 }; | |
145 | |
146 TabAnimation(TabStripGtk* tabstrip, Type type) | |
147 : tabstrip_(tabstrip), | |
148 animation_(this), | |
149 start_selected_width_(0), | |
150 start_unselected_width_(0), | |
151 end_selected_width_(0), | |
152 end_unselected_width_(0), | |
153 layout_on_completion_(false), | |
154 type_(type) { | |
155 } | |
156 virtual ~TabAnimation() {} | |
157 | |
158 Type type() const { return type_; } | |
159 | |
160 void Start() { | |
161 animation_.SetSlideDuration(GetDuration()); | |
162 animation_.SetTweenType(gfx::Tween::EASE_OUT); | |
163 if (!animation_.IsShowing()) { | |
164 animation_.Reset(); | |
165 animation_.Show(); | |
166 } | |
167 } | |
168 | |
169 void Stop() { | |
170 animation_.Stop(); | |
171 } | |
172 | |
173 void set_layout_on_completion(bool layout_on_completion) { | |
174 layout_on_completion_ = layout_on_completion; | |
175 } | |
176 | |
177 // Retrieves the width for the Tab at the specified index if an animation is | |
178 // active. | |
179 static double GetCurrentTabWidth(TabStripGtk* tabstrip, | |
180 TabStripGtk::TabAnimation* animation, | |
181 int index) { | |
182 TabGtk* tab = tabstrip->GetTabAt(index); | |
183 double tab_width; | |
184 if (tab->mini()) { | |
185 tab_width = TabGtk::GetMiniWidth(); | |
186 } else { | |
187 double unselected, selected; | |
188 tabstrip->GetCurrentTabWidths(&unselected, &selected); | |
189 tab_width = tab->IsActive() ? selected : unselected; | |
190 } | |
191 | |
192 if (animation) { | |
193 double specified_tab_width = animation->GetWidthForTab(index); | |
194 if (specified_tab_width != -1) | |
195 tab_width = specified_tab_width; | |
196 } | |
197 | |
198 return tab_width; | |
199 } | |
200 | |
201 // Overridden from gfx::AnimationDelegate: | |
202 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { | |
203 tabstrip_->AnimationLayout(end_unselected_width_); | |
204 } | |
205 | |
206 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { | |
207 tabstrip_->FinishAnimation(this, layout_on_completion_); | |
208 // This object is destroyed now, so we can't do anything else after this. | |
209 } | |
210 | |
211 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE { | |
212 AnimationEnded(animation); | |
213 } | |
214 | |
215 // Returns the gap before the tab at the specified index. Subclass if during | |
216 // an animation you need to insert a gap before a tab. | |
217 virtual double GetGapWidth(int index) { | |
218 return 0; | |
219 } | |
220 | |
221 protected: | |
222 // Returns the duration of the animation. | |
223 virtual int GetDuration() const { | |
224 return kDefaultAnimationDurationMs; | |
225 } | |
226 | |
227 // Subclasses override to return the width of the Tab at the specified index | |
228 // at the current animation frame. -1 indicates the default width should be | |
229 // used for the Tab. | |
230 virtual double GetWidthForTab(int index) const { | |
231 return -1; // Use default. | |
232 } | |
233 | |
234 // Figure out the desired start and end widths for the specified pre- and | |
235 // post- animation tab counts. | |
236 void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count, | |
237 int start_mini_count, | |
238 int end_mini_count) { | |
239 tabstrip_->GetDesiredTabWidths(start_tab_count, start_mini_count, | |
240 &start_unselected_width_, | |
241 &start_selected_width_); | |
242 double standard_tab_width = | |
243 static_cast<double>(TabRendererGtk::GetStandardSize().width()); | |
244 | |
245 if ((end_tab_count - start_tab_count) > 0 && | |
246 start_unselected_width_ < standard_tab_width) { | |
247 double minimum_tab_width = static_cast<double>( | |
248 TabRendererGtk::GetMinimumUnselectedSize().width()); | |
249 start_unselected_width_ -= minimum_tab_width / start_tab_count; | |
250 } | |
251 | |
252 tabstrip_->GenerateIdealBounds(); | |
253 tabstrip_->GetDesiredTabWidths(end_tab_count, end_mini_count, | |
254 &end_unselected_width_, | |
255 &end_selected_width_); | |
256 } | |
257 | |
258 TabStripGtk* tabstrip_; | |
259 gfx::SlideAnimation animation_; | |
260 | |
261 double start_selected_width_; | |
262 double start_unselected_width_; | |
263 double end_selected_width_; | |
264 double end_unselected_width_; | |
265 | |
266 private: | |
267 // True if a complete re-layout is required upon completion of the animation. | |
268 // Subclasses set this if they don't perform a complete layout | |
269 // themselves and canceling the animation may leave the strip in an | |
270 // inconsistent state. | |
271 bool layout_on_completion_; | |
272 | |
273 const Type type_; | |
274 | |
275 DISALLOW_COPY_AND_ASSIGN(TabAnimation); | |
276 }; | |
277 | |
278 //////////////////////////////////////////////////////////////////////////////// | |
279 | |
280 // Handles insertion of a Tab at |index|. | |
281 class InsertTabAnimation : public TabStripGtk::TabAnimation { | |
282 public: | |
283 InsertTabAnimation(TabStripGtk* tabstrip, int index) | |
284 : TabAnimation(tabstrip, INSERT), | |
285 index_(index) { | |
286 int tab_count = tabstrip->GetTabCount(); | |
287 int end_mini_count = tabstrip->GetMiniTabCount(); | |
288 int start_mini_count = end_mini_count; | |
289 if (index < end_mini_count) | |
290 start_mini_count--; | |
291 GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count, | |
292 end_mini_count); | |
293 } | |
294 virtual ~InsertTabAnimation() {} | |
295 | |
296 protected: | |
297 // Overridden from TabStripGtk::TabAnimation: | |
298 virtual double GetWidthForTab(int index) const OVERRIDE { | |
299 if (index == index_) { | |
300 bool is_selected = tabstrip_->model()->active_index() == index; | |
301 double start_width, target_width; | |
302 if (index < tabstrip_->GetMiniTabCount()) { | |
303 start_width = TabGtk::GetMinimumSelectedSize().width(); | |
304 target_width = TabGtk::GetMiniWidth(); | |
305 } else { | |
306 target_width = | |
307 is_selected ? end_unselected_width_ : end_selected_width_; | |
308 start_width = | |
309 is_selected ? TabGtk::GetMinimumSelectedSize().width() : | |
310 TabGtk::GetMinimumUnselectedSize().width(); | |
311 } | |
312 | |
313 double delta = target_width - start_width; | |
314 if (delta > 0) | |
315 return start_width + (delta * animation_.GetCurrentValue()); | |
316 | |
317 return start_width; | |
318 } | |
319 | |
320 if (tabstrip_->GetTabAt(index)->mini()) | |
321 return TabGtk::GetMiniWidth(); | |
322 | |
323 if (tabstrip_->GetTabAt(index)->IsActive()) { | |
324 double delta = end_selected_width_ - start_selected_width_; | |
325 return start_selected_width_ + (delta * animation_.GetCurrentValue()); | |
326 } | |
327 | |
328 double delta = end_unselected_width_ - start_unselected_width_; | |
329 return start_unselected_width_ + (delta * animation_.GetCurrentValue()); | |
330 } | |
331 | |
332 private: | |
333 int index_; | |
334 | |
335 DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation); | |
336 }; | |
337 | |
338 //////////////////////////////////////////////////////////////////////////////// | |
339 | |
340 // Handles removal of a Tab from |index| | |
341 class RemoveTabAnimation : public TabStripGtk::TabAnimation { | |
342 public: | |
343 RemoveTabAnimation(TabStripGtk* tabstrip, int index, WebContents* contents) | |
344 : TabAnimation(tabstrip, REMOVE), | |
345 index_(index) { | |
346 int tab_count = tabstrip->GetTabCount(); | |
347 int start_mini_count = tabstrip->GetMiniTabCount(); | |
348 int end_mini_count = start_mini_count; | |
349 if (index < start_mini_count) | |
350 end_mini_count--; | |
351 GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count, | |
352 end_mini_count); | |
353 // If the last non-mini-tab is being removed we force a layout on | |
354 // completion. This is necessary as the value returned by GetTabHOffset | |
355 // changes once the tab is actually removed (which happens at the end of | |
356 // the animation), and unless we layout GetTabHOffset won't be called after | |
357 // the removal. | |
358 // We do the same when the last mini-tab is being removed for the same | |
359 // reason. | |
360 set_layout_on_completion(start_mini_count > 0 && | |
361 (end_mini_count == 0 || | |
362 (start_mini_count == end_mini_count && | |
363 tab_count == start_mini_count + 1))); | |
364 } | |
365 | |
366 virtual ~RemoveTabAnimation() {} | |
367 | |
368 // Returns the index of the tab being removed. | |
369 int index() const { return index_; } | |
370 | |
371 protected: | |
372 // Overridden from TabStripGtk::TabAnimation: | |
373 virtual double GetWidthForTab(int index) const OVERRIDE { | |
374 TabGtk* tab = tabstrip_->GetTabAt(index); | |
375 | |
376 if (index == index_) { | |
377 // The tab(s) being removed are gradually shrunken depending on the state | |
378 // of the animation. | |
379 if (tab->mini()) { | |
380 return animation_.CurrentValueBetween(TabGtk::GetMiniWidth(), | |
381 -kTabHOffset); | |
382 } | |
383 | |
384 // Removed animated Tabs are never selected. | |
385 double start_width = start_unselected_width_; | |
386 // Make sure target_width is at least abs(kTabHOffset), otherwise if | |
387 // less than kTabHOffset during layout tabs get negatively offset. | |
388 double target_width = | |
389 std::max(abs(kTabHOffset), | |
390 TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset); | |
391 return animation_.CurrentValueBetween(start_width, target_width); | |
392 } | |
393 | |
394 if (tab->mini()) | |
395 return TabGtk::GetMiniWidth(); | |
396 | |
397 if (tabstrip_->available_width_for_tabs_ != -1 && | |
398 index_ != tabstrip_->GetTabCount() - 1) { | |
399 return TabStripGtk::TabAnimation::GetWidthForTab(index); | |
400 } | |
401 | |
402 // All other tabs are sized according to the start/end widths specified at | |
403 // the start of the animation. | |
404 if (tab->IsActive()) { | |
405 double delta = end_selected_width_ - start_selected_width_; | |
406 return start_selected_width_ + (delta * animation_.GetCurrentValue()); | |
407 } | |
408 | |
409 double delta = end_unselected_width_ - start_unselected_width_; | |
410 return start_unselected_width_ + (delta * animation_.GetCurrentValue()); | |
411 } | |
412 | |
413 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { | |
414 tabstrip_->RemoveTabAt(index_); | |
415 TabStripGtk::TabAnimation::AnimationEnded(animation); | |
416 } | |
417 | |
418 private: | |
419 int index_; | |
420 | |
421 DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation); | |
422 }; | |
423 | |
424 //////////////////////////////////////////////////////////////////////////////// | |
425 | |
426 // Handles the movement of a Tab from one position to another. | |
427 class MoveTabAnimation : public TabStripGtk::TabAnimation { | |
428 public: | |
429 MoveTabAnimation(TabStripGtk* tabstrip, int tab_a_index, int tab_b_index) | |
430 : TabAnimation(tabstrip, MOVE), | |
431 start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)), | |
432 start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) { | |
433 tab_a_ = tabstrip_->GetTabAt(tab_a_index); | |
434 tab_b_ = tabstrip_->GetTabAt(tab_b_index); | |
435 | |
436 // Since we don't do a full TabStrip re-layout, we need to force a full | |
437 // layout upon completion since we're not guaranteed to be in a good state | |
438 // if for example the animation is canceled. | |
439 set_layout_on_completion(true); | |
440 } | |
441 virtual ~MoveTabAnimation() {} | |
442 | |
443 // Overridden from gfx::AnimationDelegate: | |
444 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { | |
445 // Position Tab A | |
446 double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x(); | |
447 double delta = distance * animation_.GetCurrentValue(); | |
448 double new_x = start_tab_a_bounds_.x() + delta; | |
449 gfx::Rect bounds(Round(new_x), start_tab_a_bounds_.y(), tab_a_->width(), | |
450 tab_a_->height()); | |
451 tabstrip_->SetTabBounds(tab_a_, bounds); | |
452 | |
453 // Position Tab B | |
454 distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x(); | |
455 delta = distance * animation_.GetCurrentValue(); | |
456 new_x = start_tab_b_bounds_.x() + delta; | |
457 bounds = gfx::Rect(Round(new_x), start_tab_b_bounds_.y(), tab_b_->width(), | |
458 tab_b_->height()); | |
459 tabstrip_->SetTabBounds(tab_b_, bounds); | |
460 } | |
461 | |
462 protected: | |
463 // Overridden from TabStripGtk::TabAnimation: | |
464 virtual int GetDuration() const OVERRIDE { | |
465 return kReorderAnimationDurationMs; | |
466 } | |
467 | |
468 private: | |
469 // The two tabs being exchanged. | |
470 TabGtk* tab_a_; | |
471 TabGtk* tab_b_; | |
472 | |
473 // ...and their bounds. | |
474 gfx::Rect start_tab_a_bounds_; | |
475 gfx::Rect start_tab_b_bounds_; | |
476 | |
477 DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation); | |
478 }; | |
479 | |
480 //////////////////////////////////////////////////////////////////////////////// | |
481 | |
482 // Handles the animated resize layout of the entire TabStrip from one width | |
483 // to another. | |
484 class ResizeLayoutAnimation : public TabStripGtk::TabAnimation { | |
485 public: | |
486 explicit ResizeLayoutAnimation(TabStripGtk* tabstrip) | |
487 : TabAnimation(tabstrip, RESIZE) { | |
488 int tab_count = tabstrip->GetTabCount(); | |
489 int mini_tab_count = tabstrip->GetMiniTabCount(); | |
490 GenerateStartAndEndWidths(tab_count, tab_count, mini_tab_count, | |
491 mini_tab_count); | |
492 InitStartState(); | |
493 } | |
494 virtual ~ResizeLayoutAnimation() {} | |
495 | |
496 // Overridden from gfx::AnimationDelegate: | |
497 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { | |
498 tabstrip_->needs_resize_layout_ = false; | |
499 TabStripGtk::TabAnimation::AnimationEnded(animation); | |
500 } | |
501 | |
502 protected: | |
503 // Overridden from TabStripGtk::TabAnimation: | |
504 virtual int GetDuration() const OVERRIDE { | |
505 return kResizeLayoutAnimationDurationMs; | |
506 } | |
507 | |
508 virtual double GetWidthForTab(int index) const OVERRIDE { | |
509 TabGtk* tab = tabstrip_->GetTabAt(index); | |
510 | |
511 if (tab->mini()) | |
512 return TabGtk::GetMiniWidth(); | |
513 | |
514 if (tab->IsActive()) { | |
515 return animation_.CurrentValueBetween(start_selected_width_, | |
516 end_selected_width_); | |
517 } | |
518 | |
519 return animation_.CurrentValueBetween(start_unselected_width_, | |
520 end_unselected_width_); | |
521 } | |
522 | |
523 private: | |
524 // We need to start from the current widths of the Tabs as they were last | |
525 // laid out, _not_ the last known good state, which is what'll be done if we | |
526 // don't measure the Tab sizes here and just go with the default TabAnimation | |
527 // behavior... | |
528 void InitStartState() { | |
529 for (int i = 0; i < tabstrip_->GetTabCount(); ++i) { | |
530 TabGtk* current_tab = tabstrip_->GetTabAt(i); | |
531 if (!current_tab->mini()) { | |
532 if (current_tab->IsActive()) { | |
533 start_selected_width_ = current_tab->width(); | |
534 } else { | |
535 start_unselected_width_ = current_tab->width(); | |
536 } | |
537 } | |
538 } | |
539 } | |
540 | |
541 DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation); | |
542 }; | |
543 | |
544 // Handles a tabs mini-state changing while the tab does not change position | |
545 // in the model. | |
546 class MiniTabAnimation : public TabStripGtk::TabAnimation { | |
547 public: | |
548 MiniTabAnimation(TabStripGtk* tabstrip, int index) | |
549 : TabAnimation(tabstrip, MINI), | |
550 index_(index) { | |
551 int tab_count = tabstrip->GetTabCount(); | |
552 int start_mini_count = tabstrip->GetMiniTabCount(); | |
553 int end_mini_count = start_mini_count; | |
554 if (tabstrip->GetTabAt(index)->mini()) | |
555 start_mini_count--; | |
556 else | |
557 start_mini_count++; | |
558 tabstrip_->GetTabAt(index)->set_animating_mini_change(true); | |
559 GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count, | |
560 end_mini_count); | |
561 } | |
562 | |
563 protected: | |
564 // Overridden from TabStripGtk::TabAnimation: | |
565 virtual int GetDuration() const OVERRIDE { | |
566 return kMiniTabAnimationDurationMs; | |
567 } | |
568 | |
569 virtual double GetWidthForTab(int index) const OVERRIDE { | |
570 TabGtk* tab = tabstrip_->GetTabAt(index); | |
571 | |
572 if (index == index_) { | |
573 if (tab->mini()) { | |
574 return animation_.CurrentValueBetween( | |
575 start_selected_width_, | |
576 static_cast<double>(TabGtk::GetMiniWidth())); | |
577 } else { | |
578 return animation_.CurrentValueBetween( | |
579 static_cast<double>(TabGtk::GetMiniWidth()), | |
580 end_selected_width_); | |
581 } | |
582 } else if (tab->mini()) { | |
583 return TabGtk::GetMiniWidth(); | |
584 } | |
585 | |
586 if (tab->IsActive()) { | |
587 return animation_.CurrentValueBetween(start_selected_width_, | |
588 end_selected_width_); | |
589 } | |
590 | |
591 return animation_.CurrentValueBetween(start_unselected_width_, | |
592 end_unselected_width_); | |
593 } | |
594 | |
595 private: | |
596 // Index of the tab whose mini-state changed. | |
597 int index_; | |
598 | |
599 DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation); | |
600 }; | |
601 | |
602 //////////////////////////////////////////////////////////////////////////////// | |
603 | |
604 // Handles the animation when a tabs mini-state changes and the tab moves as a | |
605 // result. | |
606 class MiniMoveAnimation : public TabStripGtk::TabAnimation { | |
607 public: | |
608 MiniMoveAnimation(TabStripGtk* tabstrip, | |
609 int from_index, | |
610 int to_index, | |
611 const gfx::Rect& start_bounds) | |
612 : TabAnimation(tabstrip, MINI_MOVE), | |
613 tab_(tabstrip->GetTabAt(to_index)), | |
614 start_bounds_(start_bounds), | |
615 from_index_(from_index), | |
616 to_index_(to_index) { | |
617 int tab_count = tabstrip->GetTabCount(); | |
618 int start_mini_count = tabstrip->GetMiniTabCount(); | |
619 int end_mini_count = start_mini_count; | |
620 if (tabstrip->GetTabAt(to_index)->mini()) | |
621 start_mini_count--; | |
622 else | |
623 start_mini_count++; | |
624 GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count, | |
625 end_mini_count); | |
626 target_bounds_ = tabstrip->GetIdealBounds(to_index); | |
627 tab_->set_animating_mini_change(true); | |
628 } | |
629 | |
630 // Overridden from gfx::AnimationDelegate: | |
631 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { | |
632 // Do the normal layout. | |
633 TabAnimation::AnimationProgressed(animation); | |
634 | |
635 // Then special case the position of the tab being moved. | |
636 int x = animation_.CurrentValueBetween(start_bounds_.x(), | |
637 target_bounds_.x()); | |
638 int width = animation_.CurrentValueBetween(start_bounds_.width(), | |
639 target_bounds_.width()); | |
640 gfx::Rect tab_bounds(x, start_bounds_.y(), width, | |
641 start_bounds_.height()); | |
642 tabstrip_->SetTabBounds(tab_, tab_bounds); | |
643 } | |
644 | |
645 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { | |
646 tabstrip_->needs_resize_layout_ = false; | |
647 TabStripGtk::TabAnimation::AnimationEnded(animation); | |
648 } | |
649 | |
650 virtual double GetGapWidth(int index) OVERRIDE { | |
651 if (to_index_ < from_index_) { | |
652 // The tab was made mini. | |
653 if (index == to_index_) { | |
654 double current_size = | |
655 animation_.CurrentValueBetween(0, target_bounds_.width()); | |
656 if (current_size < -kTabHOffset) | |
657 return -(current_size + kTabHOffset); | |
658 } else if (index == from_index_ + 1) { | |
659 return animation_.CurrentValueBetween(start_bounds_.width(), 0); | |
660 } | |
661 } else { | |
662 // The tab was was made a normal tab. | |
663 if (index == from_index_) { | |
664 return animation_.CurrentValueBetween( | |
665 TabGtk::GetMiniWidth() + kTabHOffset, 0); | |
666 } | |
667 } | |
668 return 0; | |
669 } | |
670 | |
671 protected: | |
672 // Overridden from TabStripGtk::TabAnimation: | |
673 virtual int GetDuration() const OVERRIDE { | |
674 return kReorderAnimationDurationMs; | |
675 } | |
676 | |
677 virtual double GetWidthForTab(int index) const OVERRIDE { | |
678 TabGtk* tab = tabstrip_->GetTabAt(index); | |
679 | |
680 if (index == to_index_) | |
681 return animation_.CurrentValueBetween(0, target_bounds_.width()); | |
682 | |
683 if (tab->mini()) | |
684 return TabGtk::GetMiniWidth(); | |
685 | |
686 if (tab->IsActive()) { | |
687 return animation_.CurrentValueBetween(start_selected_width_, | |
688 end_selected_width_); | |
689 } | |
690 | |
691 return animation_.CurrentValueBetween(start_unselected_width_, | |
692 end_unselected_width_); | |
693 } | |
694 | |
695 private: | |
696 // The tab being moved. | |
697 TabGtk* tab_; | |
698 | |
699 // Initial bounds of tab_. | |
700 gfx::Rect start_bounds_; | |
701 | |
702 // Target bounds. | |
703 gfx::Rect target_bounds_; | |
704 | |
705 // Start and end indices of the tab. | |
706 int from_index_; | |
707 int to_index_; | |
708 | |
709 DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation); | |
710 }; | |
711 | |
712 //////////////////////////////////////////////////////////////////////////////// | |
713 // TabStripGtk, public: | |
714 | |
715 // static | |
716 const int TabStripGtk::mini_to_non_mini_gap_ = 3; | |
717 | |
718 TabStripGtk::TabStripGtk(TabStripModel* model, BrowserWindowGtk* window) | |
719 : current_unselected_width_(TabGtk::GetStandardSize().width()), | |
720 current_selected_width_(TabGtk::GetStandardSize().width()), | |
721 available_width_for_tabs_(-1), | |
722 needs_resize_layout_(false), | |
723 tab_vertical_offset_(0), | |
724 model_(model), | |
725 window_(window), | |
726 theme_service_(GtkThemeService::GetFrom(model->profile())), | |
727 added_as_message_loop_observer_(false), | |
728 hover_tab_selector_(model), | |
729 weak_factory_(this), | |
730 layout_factory_(this) { | |
731 } | |
732 | |
733 TabStripGtk::~TabStripGtk() { | |
734 model_->RemoveObserver(this); | |
735 tabstrip_.Destroy(); | |
736 | |
737 // Free any remaining tabs. This is needed to free the very last tab, | |
738 // because it is not animated on close. This also happens when all of the | |
739 // tabs are closed at once. | |
740 std::vector<TabData>::iterator iterator = tab_data_.begin(); | |
741 for (; iterator < tab_data_.end(); iterator++) { | |
742 delete iterator->tab; | |
743 } | |
744 | |
745 tab_data_.clear(); | |
746 | |
747 // Make sure we unhook ourselves as a message loop observer so that we don't | |
748 // crash in the case where the user closes the last tab in a window. | |
749 RemoveMessageLoopObserver(); | |
750 } | |
751 | |
752 void TabStripGtk::Init() { | |
753 model_->AddObserver(this); | |
754 | |
755 tabstrip_.Own(gtk_fixed_new()); | |
756 ViewIDUtil::SetID(tabstrip_.get(), VIEW_ID_TAB_STRIP); | |
757 // We want the tab strip to be horizontally shrinkable, so that the Chrome | |
758 // window can be resized freely. | |
759 gtk_widget_set_size_request(tabstrip_.get(), 0, | |
760 TabGtk::GetMinimumUnselectedSize().height()); | |
761 gtk_widget_set_app_paintable(tabstrip_.get(), TRUE); | |
762 gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_ALL, | |
763 NULL, 0, | |
764 static_cast<GdkDragAction>( | |
765 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)); | |
766 static const int targets[] = { ui::TEXT_URI_LIST, | |
767 ui::NETSCAPE_URL, | |
768 ui::TEXT_PLAIN, | |
769 -1 }; | |
770 ui::SetDestTargetList(tabstrip_.get(), targets); | |
771 | |
772 g_signal_connect(tabstrip_.get(), "map", | |
773 G_CALLBACK(OnMapThunk), this); | |
774 g_signal_connect(tabstrip_.get(), "expose-event", | |
775 G_CALLBACK(OnExposeThunk), this); | |
776 g_signal_connect(tabstrip_.get(), "size-allocate", | |
777 G_CALLBACK(OnSizeAllocateThunk), this); | |
778 g_signal_connect(tabstrip_.get(), "drag-motion", | |
779 G_CALLBACK(OnDragMotionThunk), this); | |
780 g_signal_connect(tabstrip_.get(), "drag-drop", | |
781 G_CALLBACK(OnDragDropThunk), this); | |
782 g_signal_connect(tabstrip_.get(), "drag-leave", | |
783 G_CALLBACK(OnDragLeaveThunk), this); | |
784 g_signal_connect(tabstrip_.get(), "drag-data-received", | |
785 G_CALLBACK(OnDragDataReceivedThunk), this); | |
786 | |
787 newtab_button_.reset(MakeNewTabButton()); | |
788 newtab_surface_bounds_.SetRect(0, 0, newtab_button_->SurfaceWidth(), | |
789 newtab_button_->SurfaceHeight()); | |
790 | |
791 gtk_widget_show_all(tabstrip_.get()); | |
792 | |
793 bounds_ = GetInitialWidgetBounds(tabstrip_.get()); | |
794 | |
795 if (drop_indicator_width == 0) { | |
796 // Direction doesn't matter, both images are the same size. | |
797 GdkPixbuf* drop_image = GetDropArrowImage(true)->ToGdkPixbuf(); | |
798 drop_indicator_width = gdk_pixbuf_get_width(drop_image); | |
799 drop_indicator_height = gdk_pixbuf_get_height(drop_image); | |
800 } | |
801 | |
802 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, | |
803 content::Source<ThemeService>(theme_service_)); | |
804 theme_service_->InitThemesFor(this); | |
805 | |
806 ViewIDUtil::SetDelegateForWidget(widget(), this); | |
807 } | |
808 | |
809 void TabStripGtk::Show() { | |
810 gtk_widget_show(tabstrip_.get()); | |
811 } | |
812 | |
813 void TabStripGtk::Hide() { | |
814 gtk_widget_hide(tabstrip_.get()); | |
815 } | |
816 | |
817 bool TabStripGtk::IsActiveDropTarget() const { | |
818 for (int i = 0; i < GetTabCount(); ++i) { | |
819 TabGtk* tab = GetTabAt(i); | |
820 if (tab->dragging()) | |
821 return true; | |
822 } | |
823 return false; | |
824 } | |
825 | |
826 void TabStripGtk::Layout() { | |
827 // Called from: | |
828 // - window resize | |
829 // - animation completion | |
830 StopAnimation(); | |
831 | |
832 GenerateIdealBounds(); | |
833 int tab_count = GetTabCount(); | |
834 int tab_right = 0; | |
835 for (int i = 0; i < tab_count; ++i) { | |
836 const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; | |
837 TabGtk* tab = GetTabAt(i); | |
838 tab->set_animating_mini_change(false); | |
839 tab->set_vertical_offset(tab_vertical_offset_); | |
840 SetTabBounds(tab, bounds); | |
841 tab_right = bounds.right(); | |
842 tab_right += GetTabHOffset(i + 1); | |
843 } | |
844 | |
845 LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_); | |
846 } | |
847 | |
848 void TabStripGtk::SchedulePaint() { | |
849 gtk_widget_queue_draw(tabstrip_.get()); | |
850 } | |
851 | |
852 void TabStripGtk::SetBounds(const gfx::Rect& bounds) { | |
853 bounds_ = bounds; | |
854 } | |
855 | |
856 void TabStripGtk::UpdateLoadingAnimations() { | |
857 for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { | |
858 TabGtk* current_tab = GetTabAt(i); | |
859 if (current_tab->closing()) { | |
860 --index; | |
861 } else { | |
862 TabRendererGtk::AnimationState state; | |
863 content::WebContents* web_contents = model_->GetWebContentsAt(index); | |
864 if (!web_contents|| !web_contents->IsLoading()) { | |
865 state = TabGtk::ANIMATION_NONE; | |
866 } else if (web_contents->IsWaitingForResponse()) { | |
867 state = TabGtk::ANIMATION_WAITING; | |
868 } else { | |
869 state = TabGtk::ANIMATION_LOADING; | |
870 } | |
871 if (current_tab->ValidateLoadingAnimation(state)) { | |
872 // Queue the tab's icon area to be repainted. | |
873 gfx::Rect favicon_bounds = current_tab->favicon_bounds(); | |
874 gtk_widget_queue_draw_area(tabstrip_.get(), | |
875 favicon_bounds.x() + current_tab->x(), | |
876 favicon_bounds.y() + current_tab->y(), | |
877 favicon_bounds.width(), | |
878 favicon_bounds.height()); | |
879 } | |
880 } | |
881 } | |
882 } | |
883 | |
884 bool TabStripGtk::IsCompatibleWith(TabStripGtk* other) { | |
885 return model_->profile() == other->model()->profile(); | |
886 } | |
887 | |
888 bool TabStripGtk::IsAnimating() const { | |
889 return active_animation_.get() != NULL; | |
890 } | |
891 | |
892 void TabStripGtk::DestroyDragController() { | |
893 drag_controller_.reset(); | |
894 } | |
895 | |
896 void TabStripGtk::DestroyDraggedTab(TabGtk* tab) { | |
897 // We could be running an animation that references this Tab. | |
898 StopAnimation(); | |
899 | |
900 // Make sure we leave the tab_data_ vector in a consistent state, otherwise | |
901 // we'll be pointing to tabs that have been deleted and removed from the | |
902 // child view list. | |
903 std::vector<TabData>::iterator it = tab_data_.begin(); | |
904 for (; it != tab_data_.end(); ++it) { | |
905 if (it->tab == tab) { | |
906 if (!model_->closing_all()) | |
907 NOTREACHED() << "Leaving in an inconsistent state!"; | |
908 tab_data_.erase(it); | |
909 break; | |
910 } | |
911 } | |
912 | |
913 gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), tab->widget()); | |
914 // If we delete the dragged source tab here, the DestroyDragWidget posted | |
915 // task will be run after the tab is deleted, leading to a crash. | |
916 base::MessageLoop::current()->DeleteSoon(FROM_HERE, tab); | |
917 | |
918 // Force a layout here, because if we've just quickly drag detached a Tab, | |
919 // the stopping of the active animation above may have left the TabStrip in a | |
920 // bad (visual) state. | |
921 Layout(); | |
922 } | |
923 | |
924 gfx::Rect TabStripGtk::GetIdealBounds(int index) { | |
925 DCHECK(index >= 0 && index < GetTabCount()); | |
926 return tab_data_.at(index).ideal_bounds; | |
927 } | |
928 | |
929 void TabStripGtk::SetVerticalOffset(int offset) { | |
930 tab_vertical_offset_ = offset; | |
931 Layout(); | |
932 } | |
933 | |
934 gfx::Point TabStripGtk::GetTabStripOriginForWidget(GtkWidget* target) { | |
935 int x, y; | |
936 GtkAllocation widget_allocation; | |
937 gtk_widget_get_allocation(widget(), &widget_allocation); | |
938 if (!gtk_widget_translate_coordinates(widget(), target, | |
939 -widget_allocation.x, 0, &x, &y)) { | |
940 // If the tab strip isn't showing, give the coordinates relative to the | |
941 // toplevel instead. | |
942 if (!gtk_widget_translate_coordinates( | |
943 gtk_widget_get_toplevel(widget()), target, 0, 0, &x, &y)) { | |
944 NOTREACHED(); | |
945 } | |
946 } | |
947 if (!gtk_widget_get_has_window(target)) { | |
948 GtkAllocation target_allocation; | |
949 gtk_widget_get_allocation(target, &target_allocation); | |
950 x += target_allocation.x; | |
951 y += target_allocation.y; | |
952 } | |
953 return gfx::Point(x, y); | |
954 } | |
955 | |
956 //////////////////////////////////////////////////////////////////////////////// | |
957 // ViewIDUtil::Delegate implementation | |
958 | |
959 GtkWidget* TabStripGtk::GetWidgetForViewID(ViewID view_id) { | |
960 if (GetTabCount() > 0) { | |
961 if (view_id == VIEW_ID_TAB_LAST) { | |
962 return GetTabAt(GetTabCount() - 1)->widget(); | |
963 } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) { | |
964 int index = view_id - VIEW_ID_TAB_0; | |
965 if (index >= 0 && index < GetTabCount()) { | |
966 return GetTabAt(index)->widget(); | |
967 } else { | |
968 return NULL; | |
969 } | |
970 } | |
971 } | |
972 | |
973 return NULL; | |
974 } | |
975 | |
976 //////////////////////////////////////////////////////////////////////////////// | |
977 // TabStripGtk, TabStripModelObserver implementation: | |
978 | |
979 void TabStripGtk::TabInsertedAt(WebContents* contents, | |
980 int index, | |
981 bool foreground) { | |
982 TRACE_EVENT0("ui::gtk", "TabStripGtk::TabInsertedAt"); | |
983 | |
984 DCHECK(contents); | |
985 DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index)); | |
986 | |
987 StopAnimation(); | |
988 | |
989 bool contains_tab = false; | |
990 TabGtk* tab = NULL; | |
991 // First see if this Tab is one that was dragged out of this TabStrip and is | |
992 // now being dragged back in. In this case, the DraggedTabController actually | |
993 // has the Tab already constructed and we can just insert it into our list | |
994 // again. | |
995 if (IsDragSessionActive()) { | |
996 tab = drag_controller_->GetDraggedTabForContents(contents); | |
997 if (tab) { | |
998 // If the Tab was detached, it would have been animated closed but not | |
999 // removed, so we need to reset this property. | |
1000 tab->set_closing(false); | |
1001 tab->ValidateLoadingAnimation(TabRendererGtk::ANIMATION_NONE); | |
1002 tab->SetVisible(true); | |
1003 } | |
1004 | |
1005 // See if we're already in the list. We don't want to add ourselves twice. | |
1006 std::vector<TabData>::const_iterator iter = tab_data_.begin(); | |
1007 for (; iter != tab_data_.end() && !contains_tab; ++iter) { | |
1008 if (iter->tab == tab) | |
1009 contains_tab = true; | |
1010 } | |
1011 } | |
1012 | |
1013 if (!tab) | |
1014 tab = new TabGtk(this); | |
1015 | |
1016 // Only insert if we're not already in the list. | |
1017 if (!contains_tab) { | |
1018 TabData d = { tab, gfx::Rect() }; | |
1019 tab_data_.insert(tab_data_.begin() + index, d); | |
1020 tab->UpdateData(contents, model_->IsAppTab(index), false); | |
1021 } | |
1022 tab->set_mini(model_->IsMiniTab(index)); | |
1023 tab->set_app(model_->IsAppTab(index)); | |
1024 tab->SetBlocked(model_->IsTabBlocked(index)); | |
1025 | |
1026 if (gtk_widget_get_parent(tab->widget()) != tabstrip_.get()) | |
1027 gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0); | |
1028 | |
1029 // Don't animate the first tab; it looks weird. | |
1030 if (GetTabCount() > 1) { | |
1031 StartInsertTabAnimation(index); | |
1032 // We added the tab at 0x0, we need to force an animation step otherwise | |
1033 // if GTK paints before the animation event the tab is painted at 0x0 | |
1034 // which is most likely not where it should be positioned. | |
1035 active_animation_->AnimationProgressed(NULL); | |
1036 } else { | |
1037 Layout(); | |
1038 } | |
1039 | |
1040 ReStack(); | |
1041 } | |
1042 | |
1043 void TabStripGtk::TabDetachedAt(WebContents* contents, int index) { | |
1044 GenerateIdealBounds(); | |
1045 StartRemoveTabAnimation(index, contents); | |
1046 // Have to do this _after_ calling StartRemoveTabAnimation, so that any | |
1047 // previous remove is completed fully and index is valid in sync with the | |
1048 // model index. | |
1049 GetTabAt(index)->set_closing(true); | |
1050 } | |
1051 | |
1052 void TabStripGtk::ActiveTabChanged(WebContents* old_contents, | |
1053 WebContents* new_contents, | |
1054 int index, | |
1055 int reason) { | |
1056 TRACE_EVENT0("ui::gtk", "TabStripGtk::ActiveTabChanged"); | |
1057 ReStack(); | |
1058 } | |
1059 | |
1060 void TabStripGtk::TabSelectionChanged(TabStripModel* tab_strip_model, | |
1061 const ui::ListSelectionModel& old_model) { | |
1062 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are | |
1063 // a different size to the selected ones. | |
1064 bool tiny_tabs = current_unselected_width_ != current_selected_width_; | |
1065 if (!IsAnimating() && (!needs_resize_layout_ || tiny_tabs)) | |
1066 Layout(); | |
1067 | |
1068 if (model_->active_index() >= 0) | |
1069 GetTabAt(model_->active_index())->SchedulePaint(); | |
1070 | |
1071 if (old_model.active() >= 0) { | |
1072 GetTabAt(old_model.active())->SchedulePaint(); | |
1073 GetTabAt(old_model.active())->StopMiniTabTitleAnimation(); | |
1074 } | |
1075 | |
1076 std::vector<int> indices_affected; | |
1077 std::insert_iterator<std::vector<int> > it1(indices_affected, | |
1078 indices_affected.begin()); | |
1079 std::set_symmetric_difference( | |
1080 old_model.selected_indices().begin(), | |
1081 old_model.selected_indices().end(), | |
1082 model_->selection_model().selected_indices().begin(), | |
1083 model_->selection_model().selected_indices().end(), | |
1084 it1); | |
1085 for (std::vector<int>::iterator it = indices_affected.begin(); | |
1086 it != indices_affected.end(); ++it) { | |
1087 // SchedulePaint() has already been called for the active tab and | |
1088 // the previously active tab (if it still exists). | |
1089 if (*it != model_->active_index() && *it != old_model.active()) | |
1090 GetTabAtAdjustForAnimation(*it)->SchedulePaint(); | |
1091 } | |
1092 | |
1093 ui::ListSelectionModel::SelectedIndices no_longer_selected = | |
1094 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>( | |
1095 old_model.selected_indices(), | |
1096 model_->selection_model().selected_indices()); | |
1097 for (std::vector<int>::iterator it = no_longer_selected.begin(); | |
1098 it != no_longer_selected.end(); ++it) { | |
1099 GetTabAtAdjustForAnimation(*it)->StopMiniTabTitleAnimation(); | |
1100 } | |
1101 } | |
1102 | |
1103 void TabStripGtk::TabMoved(WebContents* contents, | |
1104 int from_index, | |
1105 int to_index) { | |
1106 gfx::Rect start_bounds = GetIdealBounds(from_index); | |
1107 TabGtk* tab = GetTabAt(from_index); | |
1108 tab_data_.erase(tab_data_.begin() + from_index); | |
1109 TabData data = {tab, gfx::Rect()}; | |
1110 tab->set_mini(model_->IsMiniTab(to_index)); | |
1111 tab->SetBlocked(model_->IsTabBlocked(to_index)); | |
1112 tab_data_.insert(tab_data_.begin() + to_index, data); | |
1113 GenerateIdealBounds(); | |
1114 StartMoveTabAnimation(from_index, to_index); | |
1115 ReStack(); | |
1116 } | |
1117 | |
1118 void TabStripGtk::TabChangedAt(WebContents* contents, | |
1119 int index, | |
1120 TabChangeType change_type) { | |
1121 // Index is in terms of the model. Need to make sure we adjust that index in | |
1122 // case we have an animation going. | |
1123 TabGtk* tab = GetTabAtAdjustForAnimation(index); | |
1124 if (change_type == TITLE_NOT_LOADING) { | |
1125 if (tab->mini() && !tab->IsActive()) | |
1126 tab->StartMiniTabTitleAnimation(); | |
1127 // We'll receive another notification of the change asynchronously. | |
1128 return; | |
1129 } | |
1130 tab->UpdateData(contents, | |
1131 model_->IsAppTab(index), | |
1132 change_type == LOADING_ONLY); | |
1133 tab->UpdateFromModel(); | |
1134 } | |
1135 | |
1136 void TabStripGtk::TabReplacedAt(TabStripModel* tab_strip_model, | |
1137 WebContents* old_contents, | |
1138 WebContents* new_contents, | |
1139 int index) { | |
1140 TabChangedAt(new_contents, index, ALL); | |
1141 } | |
1142 | |
1143 void TabStripGtk::TabMiniStateChanged(WebContents* contents, int index) { | |
1144 // Don't do anything if we've already picked up the change from TabMoved. | |
1145 if (GetTabAt(index)->mini() == model_->IsMiniTab(index)) | |
1146 return; | |
1147 | |
1148 GetTabAt(index)->set_mini(model_->IsMiniTab(index)); | |
1149 // Don't animate if the window isn't visible yet. The window won't be visible | |
1150 // when dragging a mini-tab to a new window. | |
1151 if (window_ && window_->window() && | |
1152 gtk_widget_get_visible(GTK_WIDGET(window_->window()))) { | |
1153 StartMiniTabAnimation(index); | |
1154 } else { | |
1155 Layout(); | |
1156 } | |
1157 } | |
1158 | |
1159 void TabStripGtk::TabBlockedStateChanged(WebContents* contents, int index) { | |
1160 GetTabAt(index)->SetBlocked(model_->IsTabBlocked(index)); | |
1161 } | |
1162 | |
1163 //////////////////////////////////////////////////////////////////////////////// | |
1164 // TabStripGtk, TabGtk::TabDelegate implementation: | |
1165 | |
1166 bool TabStripGtk::IsTabActive(const TabGtk* tab) const { | |
1167 if (tab->closing()) | |
1168 return false; | |
1169 | |
1170 return GetIndexOfTab(tab) == model_->active_index(); | |
1171 } | |
1172 | |
1173 bool TabStripGtk::IsTabSelected(const TabGtk* tab) const { | |
1174 if (tab->closing()) | |
1175 return false; | |
1176 | |
1177 return model_->IsTabSelected(GetIndexOfTab(tab)); | |
1178 } | |
1179 | |
1180 bool TabStripGtk::IsTabDetached(const TabGtk* tab) const { | |
1181 if (drag_controller_.get()) | |
1182 return drag_controller_->IsTabDetached(tab); | |
1183 return false; | |
1184 } | |
1185 | |
1186 void TabStripGtk::GetCurrentTabWidths(double* unselected_width, | |
1187 double* selected_width) const { | |
1188 *unselected_width = current_unselected_width_; | |
1189 *selected_width = current_selected_width_; | |
1190 } | |
1191 | |
1192 bool TabStripGtk::IsTabPinned(const TabGtk* tab) const { | |
1193 if (tab->closing()) | |
1194 return false; | |
1195 | |
1196 return model_->IsTabPinned(GetIndexOfTab(tab)); | |
1197 } | |
1198 | |
1199 void TabStripGtk::ActivateTab(TabGtk* tab) { | |
1200 int index = GetIndexOfTab(tab); | |
1201 if (model_->ContainsIndex(index)) | |
1202 model_->ActivateTabAt(index, true); | |
1203 } | |
1204 | |
1205 void TabStripGtk::ToggleTabSelection(TabGtk* tab) { | |
1206 int index = GetIndexOfTab(tab); | |
1207 model_->ToggleSelectionAt(index); | |
1208 } | |
1209 | |
1210 void TabStripGtk::ExtendTabSelection(TabGtk* tab) { | |
1211 int index = GetIndexOfTab(tab); | |
1212 if (model_->ContainsIndex(index)) | |
1213 model_->ExtendSelectionTo(index); | |
1214 } | |
1215 | |
1216 void TabStripGtk::CloseTab(TabGtk* tab) { | |
1217 int tab_index = GetIndexOfTab(tab); | |
1218 if (model_->ContainsIndex(tab_index)) { | |
1219 TabGtk* last_tab = GetTabAt(GetTabCount() - 1); | |
1220 // Limit the width available to the TabStrip for laying out Tabs, so that | |
1221 // Tabs are not resized until a later time (when the mouse pointer leaves | |
1222 // the TabStrip). | |
1223 available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab); | |
1224 needs_resize_layout_ = true; | |
1225 // We hook into the message loop in order to receive mouse move events when | |
1226 // the mouse is outside of the tabstrip. We unhook once the resize layout | |
1227 // animation is started. | |
1228 AddMessageLoopObserver(); | |
1229 model_->CloseWebContentsAt(tab_index, | |
1230 TabStripModel::CLOSE_USER_GESTURE | | |
1231 TabStripModel::CLOSE_CREATE_HISTORICAL_TAB); | |
1232 } | |
1233 } | |
1234 | |
1235 bool TabStripGtk::IsCommandEnabledForTab( | |
1236 TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const { | |
1237 int index = GetIndexOfTab(tab); | |
1238 if (model_->ContainsIndex(index)) | |
1239 return model_->IsContextMenuCommandEnabled(index, command_id); | |
1240 return false; | |
1241 } | |
1242 | |
1243 void TabStripGtk::ExecuteCommandForTab( | |
1244 TabStripModel::ContextMenuCommand command_id, TabGtk* tab) { | |
1245 int index = GetIndexOfTab(tab); | |
1246 if (model_->ContainsIndex(index)) | |
1247 model_->ExecuteContextMenuCommand(index, command_id); | |
1248 } | |
1249 | |
1250 void TabStripGtk::StartHighlightTabsForCommand( | |
1251 TabStripModel::ContextMenuCommand command_id, TabGtk* tab) { | |
1252 if (command_id == TabStripModel::CommandCloseOtherTabs || | |
1253 command_id == TabStripModel::CommandCloseTabsToRight) { | |
1254 NOTIMPLEMENTED(); | |
1255 } | |
1256 } | |
1257 | |
1258 void TabStripGtk::StopHighlightTabsForCommand( | |
1259 TabStripModel::ContextMenuCommand command_id, TabGtk* tab) { | |
1260 if (command_id == TabStripModel::CommandCloseTabsToRight || | |
1261 command_id == TabStripModel::CommandCloseOtherTabs) { | |
1262 // Just tell all Tabs to stop pulsing - it's safe. | |
1263 StopAllHighlighting(); | |
1264 } | |
1265 } | |
1266 | |
1267 void TabStripGtk::StopAllHighlighting() { | |
1268 // TODO(jhawkins): Hook up animations. | |
1269 NOTIMPLEMENTED(); | |
1270 } | |
1271 | |
1272 void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) { | |
1273 // Don't accidentally start any drag operations during animations if the | |
1274 // mouse is down. | |
1275 if (IsAnimating() || tab->closing() || !HasAvailableDragActions()) | |
1276 return; | |
1277 | |
1278 std::vector<TabGtk*> tabs; | |
1279 for (size_t i = 0; i < model()->selection_model().size(); i++) { | |
1280 TabGtk* selected_tab = | |
1281 GetTabAtAdjustForAnimation( | |
1282 model()->selection_model().selected_indices()[i]); | |
1283 if (!selected_tab->closing()) | |
1284 tabs.push_back(selected_tab); | |
1285 } | |
1286 | |
1287 drag_controller_.reset(new DraggedTabControllerGtk(this, tab, tabs)); | |
1288 drag_controller_->CaptureDragInfo(point); | |
1289 } | |
1290 | |
1291 void TabStripGtk::ContinueDrag(GdkDragContext* context) { | |
1292 // We can get called even if |MaybeStartDrag| wasn't called in the event of | |
1293 // a TabStrip animation when the mouse button is down. In this case we should | |
1294 // _not_ continue the drag because it can lead to weird bugs. | |
1295 if (drag_controller_.get()) | |
1296 drag_controller_->Drag(); | |
1297 } | |
1298 | |
1299 bool TabStripGtk::EndDrag(bool canceled) { | |
1300 return drag_controller_.get() ? drag_controller_->EndDrag(canceled) : false; | |
1301 } | |
1302 | |
1303 bool TabStripGtk::HasAvailableDragActions() const { | |
1304 return model_->delegate()->GetDragActions() != 0; | |
1305 } | |
1306 | |
1307 GtkThemeService* TabStripGtk::GetThemeProvider() { | |
1308 return theme_service_; | |
1309 } | |
1310 | |
1311 TabStripMenuController* TabStripGtk::GetTabStripMenuControllerForTab( | |
1312 TabGtk* tab) { | |
1313 return new TabStripMenuController(tab, model(), GetIndexOfTab(tab)); | |
1314 } | |
1315 | |
1316 /////////////////////////////////////////////////////////////////////////////// | |
1317 // TabStripGtk, MessageLoop::Observer implementation: | |
1318 | |
1319 void TabStripGtk::WillProcessEvent(GdkEvent* event) { | |
1320 // Nothing to do. | |
1321 } | |
1322 | |
1323 void TabStripGtk::DidProcessEvent(GdkEvent* event) { | |
1324 switch (event->type) { | |
1325 case GDK_MOTION_NOTIFY: | |
1326 case GDK_LEAVE_NOTIFY: | |
1327 HandleGlobalMouseMoveEvent(); | |
1328 break; | |
1329 default: | |
1330 break; | |
1331 } | |
1332 } | |
1333 | |
1334 /////////////////////////////////////////////////////////////////////////////// | |
1335 // TabStripGtk, content::NotificationObserver implementation: | |
1336 | |
1337 void TabStripGtk::Observe(int type, | |
1338 const content::NotificationSource& source, | |
1339 const content::NotificationDetails& details) { | |
1340 DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED); | |
1341 SetNewTabButtonBackground(); | |
1342 } | |
1343 | |
1344 //////////////////////////////////////////////////////////////////////////////// | |
1345 // TabStripGtk, private: | |
1346 | |
1347 int TabStripGtk::GetTabCount() const { | |
1348 return static_cast<int>(tab_data_.size()); | |
1349 } | |
1350 | |
1351 int TabStripGtk::GetMiniTabCount() const { | |
1352 int mini_count = 0; | |
1353 for (size_t i = 0; i < tab_data_.size(); ++i) { | |
1354 if (tab_data_[i].tab->mini()) | |
1355 mini_count++; | |
1356 else | |
1357 return mini_count; | |
1358 } | |
1359 return mini_count; | |
1360 } | |
1361 | |
1362 int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const { | |
1363 if (!base::i18n::IsRTL()) | |
1364 return last_tab->x() - bounds_.x() + last_tab->width(); | |
1365 else | |
1366 return bounds_.width() - last_tab->x(); | |
1367 } | |
1368 | |
1369 int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const { | |
1370 for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { | |
1371 TabGtk* current_tab = GetTabAt(i); | |
1372 if (current_tab->closing()) { | |
1373 --index; | |
1374 } else if (current_tab == tab) { | |
1375 return index; | |
1376 } | |
1377 } | |
1378 return -1; | |
1379 } | |
1380 | |
1381 TabGtk* TabStripGtk::GetTabAt(int index) const { | |
1382 DCHECK_GE(index, 0); | |
1383 DCHECK_LT(index, GetTabCount()); | |
1384 return tab_data_.at(index).tab; | |
1385 } | |
1386 | |
1387 TabGtk* TabStripGtk::GetTabAtAdjustForAnimation(int index) const { | |
1388 if (active_animation_.get() && | |
1389 active_animation_->type() == TabAnimation::REMOVE && | |
1390 index >= | |
1391 static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) { | |
1392 index++; | |
1393 } | |
1394 return GetTabAt(index); | |
1395 } | |
1396 | |
1397 void TabStripGtk::RemoveTabAt(int index) { | |
1398 TabGtk* removed = tab_data_.at(index).tab; | |
1399 | |
1400 // Remove the Tab from the TabStrip's list. | |
1401 tab_data_.erase(tab_data_.begin() + index); | |
1402 | |
1403 if (!removed->dragging()) { | |
1404 gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), removed->widget()); | |
1405 delete removed; | |
1406 } | |
1407 } | |
1408 | |
1409 void TabStripGtk::HandleGlobalMouseMoveEvent() { | |
1410 if (!IsCursorInTabStripZone()) { | |
1411 // Mouse moved outside the tab slop zone, start a timer to do a resize | |
1412 // layout after a short while... | |
1413 if (!weak_factory_.HasWeakPtrs()) { | |
1414 base::MessageLoop::current()->PostDelayedTask( | |
1415 FROM_HERE, | |
1416 base::Bind(&TabStripGtk::ResizeLayoutTabs, | |
1417 weak_factory_.GetWeakPtr()), | |
1418 base::TimeDelta::FromMilliseconds(kResizeTabsTimeMs)); | |
1419 } | |
1420 } else { | |
1421 // Mouse moved quickly out of the tab strip and then into it again, so | |
1422 // cancel the timer so that the strip doesn't move when the mouse moves | |
1423 // back over it. | |
1424 weak_factory_.InvalidateWeakPtrs(); | |
1425 } | |
1426 } | |
1427 | |
1428 void TabStripGtk::GenerateIdealBounds() { | |
1429 int tab_count = GetTabCount(); | |
1430 double unselected, selected; | |
1431 GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected); | |
1432 | |
1433 current_unselected_width_ = unselected; | |
1434 current_selected_width_ = selected; | |
1435 | |
1436 // NOTE: This currently assumes a tab's height doesn't differ based on | |
1437 // selected state or the number of tabs in the strip! | |
1438 int tab_height = TabGtk::GetStandardSize().height(); | |
1439 double tab_x = tab_start_x(); | |
1440 for (int i = 0; i < tab_count; ++i) { | |
1441 TabGtk* tab = GetTabAt(i); | |
1442 double tab_width = unselected; | |
1443 if (tab->mini()) | |
1444 tab_width = TabGtk::GetMiniWidth(); | |
1445 else if (tab->IsActive()) | |
1446 tab_width = selected; | |
1447 double end_of_tab = tab_x + tab_width; | |
1448 int rounded_tab_x = Round(tab_x); | |
1449 gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, | |
1450 tab_height); | |
1451 tab_data_.at(i).ideal_bounds = state; | |
1452 tab_x = end_of_tab + GetTabHOffset(i + 1); | |
1453 } | |
1454 } | |
1455 | |
1456 void TabStripGtk::LayoutNewTabButton(double last_tab_right, | |
1457 double unselected_width) { | |
1458 GtkWidget* toplevel = gtk_widget_get_ancestor(widget(), GTK_TYPE_WINDOW); | |
1459 bool is_maximized = false; | |
1460 if (toplevel) { | |
1461 GdkWindow* gdk_window = gtk_widget_get_window(toplevel); | |
1462 is_maximized = (gdk_window_get_state(gdk_window) & | |
1463 GDK_WINDOW_STATE_MAXIMIZED) != 0; | |
1464 } | |
1465 | |
1466 int y = is_maximized ? 0 : kNewTabButtonVOffset; | |
1467 int height = newtab_surface_bounds_.height() + kNewTabButtonVOffset - y; | |
1468 | |
1469 gfx::Rect bounds(0, y, newtab_surface_bounds_.width(), height); | |
1470 int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width()); | |
1471 if (delta > 1 && !needs_resize_layout_) { | |
1472 // We're shrinking tabs, so we need to anchor the New Tab button to the | |
1473 // right edge of the TabStrip's bounds, rather than the right edge of the | |
1474 // right-most Tab, otherwise it'll bounce when animating. | |
1475 bounds.set_x(bounds_.width() - newtab_button_->WidgetAllocation().width); | |
1476 } else { | |
1477 bounds.set_x(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset); | |
1478 } | |
1479 bounds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds)); | |
1480 | |
1481 gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_->widget(), | |
1482 bounds.x(), bounds.y()); | |
1483 gtk_widget_set_size_request(newtab_button_->widget(), bounds.width(), | |
1484 bounds.height()); | |
1485 } | |
1486 | |
1487 void TabStripGtk::GetDesiredTabWidths(int tab_count, | |
1488 int mini_tab_count, | |
1489 double* unselected_width, | |
1490 double* selected_width) const { | |
1491 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count); | |
1492 const double min_unselected_width = | |
1493 TabGtk::GetMinimumUnselectedSize().width(); | |
1494 const double min_selected_width = | |
1495 TabGtk::GetMinimumSelectedSize().width(); | |
1496 | |
1497 *unselected_width = min_unselected_width; | |
1498 *selected_width = min_selected_width; | |
1499 | |
1500 if (tab_count == 0) { | |
1501 // Return immediately to avoid divide-by-zero below. | |
1502 return; | |
1503 } | |
1504 | |
1505 // Determine how much space we can actually allocate to tabs. | |
1506 GtkAllocation tabstrip_allocation; | |
1507 gtk_widget_get_allocation(tabstrip_.get(), &tabstrip_allocation); | |
1508 int available_width = tabstrip_allocation.width; | |
1509 if (available_width_for_tabs_ < 0) { | |
1510 available_width = bounds_.width(); | |
1511 available_width -= | |
1512 (kNewTabButtonHOffset + newtab_button_->WidgetAllocation().width); | |
1513 } else { | |
1514 // Interesting corner case: if |available_width_for_tabs_| > the result | |
1515 // of the calculation in the conditional arm above, the strip is in | |
1516 // overflow. We can either use the specified width or the true available | |
1517 // width here; the first preserves the consistent "leave the last tab under | |
1518 // the user's mouse so they can close many tabs" behavior at the cost of | |
1519 // prolonging the glitchy appearance of the overflow state, while the second | |
1520 // gets us out of overflow as soon as possible but forces the user to move | |
1521 // their mouse for a few tabs' worth of closing. We choose visual | |
1522 // imperfection over behavioral imperfection and select the first option. | |
1523 available_width = available_width_for_tabs_; | |
1524 } | |
1525 | |
1526 if (mini_tab_count > 0) { | |
1527 available_width -= mini_tab_count * (TabGtk::GetMiniWidth() + kTabHOffset); | |
1528 tab_count -= mini_tab_count; | |
1529 if (tab_count == 0) { | |
1530 *selected_width = *unselected_width = TabGtk::GetStandardSize().width(); | |
1531 return; | |
1532 } | |
1533 // Account for gap between the last mini-tab and first normal tab. | |
1534 available_width -= mini_to_non_mini_gap_; | |
1535 } | |
1536 | |
1537 // Calculate the desired tab widths by dividing the available space into equal | |
1538 // portions. Don't let tabs get larger than the "standard width" or smaller | |
1539 // than the minimum width for each type, respectively. | |
1540 const int total_offset = kTabHOffset * (tab_count - 1); | |
1541 const double desired_tab_width = std::min( | |
1542 (static_cast<double>(available_width - total_offset) / | |
1543 static_cast<double>(tab_count)), | |
1544 static_cast<double>(TabGtk::GetStandardSize().width())); | |
1545 | |
1546 *unselected_width = std::max(desired_tab_width, min_unselected_width); | |
1547 *selected_width = std::max(desired_tab_width, min_selected_width); | |
1548 | |
1549 // When there are multiple tabs, we'll have one selected and some unselected | |
1550 // tabs. If the desired width was between the minimum sizes of these types, | |
1551 // try to shrink the tabs with the smaller minimum. For example, if we have | |
1552 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If | |
1553 // selected tabs have a minimum width of 4 and unselected tabs have a minimum | |
1554 // width of 1, the above code would set *unselected_width = 2.5, | |
1555 // *selected_width = 4, which results in a total width of 11.5. Instead, we | |
1556 // want to set *unselected_width = 2, *selected_width = 4, for a total width | |
1557 // of 10. | |
1558 if (tab_count > 1) { | |
1559 if ((min_unselected_width < min_selected_width) && | |
1560 (desired_tab_width < min_selected_width)) { | |
1561 double calc_width = | |
1562 static_cast<double>( | |
1563 available_width - total_offset - min_selected_width) / | |
1564 static_cast<double>(tab_count - 1); | |
1565 *unselected_width = std::max(calc_width, min_unselected_width); | |
1566 } else if ((min_unselected_width > min_selected_width) && | |
1567 (desired_tab_width < min_unselected_width)) { | |
1568 *selected_width = std::max(available_width - total_offset - | |
1569 (min_unselected_width * (tab_count - 1)), min_selected_width); | |
1570 } | |
1571 } | |
1572 } | |
1573 | |
1574 int TabStripGtk::GetTabHOffset(int tab_index) { | |
1575 if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() && | |
1576 !GetTabAt(tab_index)->mini()) { | |
1577 return mini_to_non_mini_gap_ + kTabHOffset; | |
1578 } | |
1579 return kTabHOffset; | |
1580 } | |
1581 | |
1582 int TabStripGtk::tab_start_x() const { | |
1583 return 0; | |
1584 } | |
1585 | |
1586 void TabStripGtk::ResizeLayoutTabs() { | |
1587 weak_factory_.InvalidateWeakPtrs(); | |
1588 layout_factory_.InvalidateWeakPtrs(); | |
1589 | |
1590 // It is critically important that this is unhooked here, otherwise we will | |
1591 // keep spying on messages forever. | |
1592 RemoveMessageLoopObserver(); | |
1593 | |
1594 available_width_for_tabs_ = -1; | |
1595 int mini_tab_count = GetMiniTabCount(); | |
1596 if (mini_tab_count == GetTabCount()) { | |
1597 // Only mini tabs, we know the tab widths won't have changed (all mini-tabs | |
1598 // have the same width), so there is nothing to do. | |
1599 return; | |
1600 } | |
1601 TabGtk* first_tab = GetTabAt(mini_tab_count); | |
1602 double unselected, selected; | |
1603 GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected); | |
1604 int w = Round(first_tab->IsActive() ? selected : unselected); | |
1605 | |
1606 // We only want to run the animation if we're not already at the desired | |
1607 // size. | |
1608 if (abs(first_tab->width() - w) > 1) | |
1609 StartResizeLayoutAnimation(); | |
1610 } | |
1611 | |
1612 bool TabStripGtk::IsCursorInTabStripZone() const { | |
1613 gfx::Point tabstrip_topleft; | |
1614 gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &tabstrip_topleft); | |
1615 | |
1616 gfx::Rect bds = bounds(); | |
1617 bds.set_origin(tabstrip_topleft); | |
1618 bds.set_height(bds.height() + kTabStripAnimationVSlop); | |
1619 | |
1620 GdkScreen* screen = gdk_screen_get_default(); | |
1621 GdkDisplay* display = gdk_screen_get_display(screen); | |
1622 gint x, y; | |
1623 gdk_display_get_pointer(display, NULL, &x, &y, NULL); | |
1624 gfx::Point cursor_point(x, y); | |
1625 | |
1626 return bds.Contains(cursor_point); | |
1627 } | |
1628 | |
1629 void TabStripGtk::ReStack() { | |
1630 TRACE_EVENT0("ui::gtk", "TabStripGtk::ReStack"); | |
1631 | |
1632 if (!gtk_widget_get_realized(tabstrip_.get())) { | |
1633 // If the window isn't realized yet, we can't stack them yet. It will be | |
1634 // done by the OnMap signal handler. | |
1635 return; | |
1636 } | |
1637 int tab_count = GetTabCount(); | |
1638 TabGtk* active_tab = NULL; | |
1639 for (int i = tab_count - 1; i >= 0; --i) { | |
1640 TabGtk* tab = GetTabAt(i); | |
1641 if (tab->IsActive()) | |
1642 active_tab = tab; | |
1643 else | |
1644 tab->Raise(); | |
1645 } | |
1646 if (active_tab) | |
1647 active_tab->Raise(); | |
1648 } | |
1649 | |
1650 void TabStripGtk::AddMessageLoopObserver() { | |
1651 if (!added_as_message_loop_observer_) { | |
1652 base::MessageLoopForUI::current()->AddObserver(this); | |
1653 added_as_message_loop_observer_ = true; | |
1654 } | |
1655 } | |
1656 | |
1657 void TabStripGtk::RemoveMessageLoopObserver() { | |
1658 if (added_as_message_loop_observer_) { | |
1659 base::MessageLoopForUI::current()->RemoveObserver(this); | |
1660 added_as_message_loop_observer_ = false; | |
1661 } | |
1662 } | |
1663 | |
1664 gfx::Rect TabStripGtk::GetDropBounds(int drop_index, | |
1665 bool drop_before, | |
1666 bool* is_beneath) { | |
1667 DCHECK_NE(drop_index, -1); | |
1668 int center_x; | |
1669 if (drop_index < GetTabCount()) { | |
1670 TabGtk* tab = GetTabAt(drop_index); | |
1671 gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get()); | |
1672 // TODO(sky): update these for pinned tabs. | |
1673 if (drop_before) | |
1674 center_x = bounds.x() - (kTabHOffset / 2); | |
1675 else | |
1676 center_x = bounds.x() + (bounds.width() / 2); | |
1677 } else { | |
1678 TabGtk* last_tab = GetTabAt(drop_index - 1); | |
1679 gfx::Rect bounds = last_tab->GetNonMirroredBounds(tabstrip_.get()); | |
1680 center_x = bounds.x() + bounds.width() + (kTabHOffset / 2); | |
1681 } | |
1682 | |
1683 center_x = gtk_util::MirroredXCoordinate(tabstrip_.get(), center_x); | |
1684 | |
1685 // Determine the screen bounds. | |
1686 gfx::Point drop_loc(center_x - drop_indicator_width / 2, | |
1687 -drop_indicator_height); | |
1688 gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc); | |
1689 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, | |
1690 drop_indicator_height); | |
1691 | |
1692 // TODO(jhawkins): We always display the arrow underneath the tab because we | |
1693 // don't have custom frame support yet. | |
1694 *is_beneath = true; | |
1695 if (*is_beneath) | |
1696 drop_bounds.Offset(0, drop_bounds.height() + bounds().height()); | |
1697 | |
1698 return drop_bounds; | |
1699 } | |
1700 | |
1701 void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) { | |
1702 // If the UI layout is right-to-left, we need to mirror the mouse | |
1703 // coordinates since we calculate the drop index based on the | |
1704 // original (and therefore non-mirrored) positions of the tabs. | |
1705 x = gtk_util::MirroredXCoordinate(tabstrip_.get(), x); | |
1706 // We don't allow replacing the urls of mini-tabs. | |
1707 for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) { | |
1708 TabGtk* tab = GetTabAt(i); | |
1709 gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get()); | |
1710 const int tab_max_x = bounds.x() + bounds.width(); | |
1711 const int hot_width = bounds.width() / kTabEdgeRatioInverse; | |
1712 if (x < tab_max_x) { | |
1713 if (x < bounds.x() + hot_width) | |
1714 SetDropIndex(i, true); | |
1715 else if (x >= tab_max_x - hot_width) | |
1716 SetDropIndex(i + 1, true); | |
1717 else | |
1718 SetDropIndex(i, false); | |
1719 return; | |
1720 } | |
1721 } | |
1722 | |
1723 // The drop isn't over a tab, add it to the end. | |
1724 SetDropIndex(GetTabCount(), true); | |
1725 } | |
1726 | |
1727 void TabStripGtk::SetDropIndex(int index, bool drop_before) { | |
1728 bool is_beneath; | |
1729 gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath); | |
1730 | |
1731 // Perform a delayed tab transition if hovering directly over a tab; | |
1732 // otherwise, cancel the pending one. | |
1733 if (index != -1 && !drop_before) | |
1734 hover_tab_selector_.StartTabTransition(index); | |
1735 else | |
1736 hover_tab_selector_.CancelTabTransition(); | |
1737 | |
1738 if (!drop_info_.get()) { | |
1739 drop_info_.reset(new DropInfo(index, drop_before, !is_beneath)); | |
1740 } else { | |
1741 if (!GTK_IS_WIDGET(drop_info_->container)) { | |
1742 drop_info_->CreateContainer(); | |
1743 } else if (drop_info_->drop_index == index && | |
1744 drop_info_->drop_before == drop_before) { | |
1745 return; | |
1746 } | |
1747 | |
1748 drop_info_->drop_index = index; | |
1749 drop_info_->drop_before = drop_before; | |
1750 if (is_beneath == drop_info_->point_down) { | |
1751 drop_info_->point_down = !is_beneath; | |
1752 drop_info_->drop_arrow = GetDropArrowImage(drop_info_->point_down); | |
1753 } | |
1754 } | |
1755 | |
1756 gtk_window_move(GTK_WINDOW(drop_info_->container), | |
1757 drop_bounds.x(), drop_bounds.y()); | |
1758 gtk_window_resize(GTK_WINDOW(drop_info_->container), | |
1759 drop_bounds.width(), drop_bounds.height()); | |
1760 } | |
1761 | |
1762 bool TabStripGtk::CompleteDrop(const guchar* data, bool is_plain_text) { | |
1763 if (!drop_info_.get()) | |
1764 return false; | |
1765 | |
1766 const int drop_index = drop_info_->drop_index; | |
1767 const bool drop_before = drop_info_->drop_before; | |
1768 | |
1769 // Destroy the drop indicator. | |
1770 drop_info_.reset(); | |
1771 | |
1772 // Cancel any pending tab transition. | |
1773 hover_tab_selector_.CancelTabTransition(); | |
1774 | |
1775 GURL url; | |
1776 if (is_plain_text) { | |
1777 AutocompleteMatch match; | |
1778 AutocompleteClassifierFactory::GetForProfile(model_->profile())->Classify( | |
1779 base::UTF8ToUTF16(reinterpret_cast<const char*>(data)), | |
1780 false, false, AutocompleteInput::INVALID_SPEC, &match, NULL); | |
1781 url = match.destination_url; | |
1782 } else { | |
1783 std::string url_string(reinterpret_cast<const char*>(data)); | |
1784 url = GURL(url_string.substr(0, url_string.find_first_of('\n'))); | |
1785 } | |
1786 if (!url.is_valid()) | |
1787 return false; | |
1788 | |
1789 chrome::NavigateParams params(window()->browser(), url, | |
1790 content::PAGE_TRANSITION_LINK); | |
1791 params.tabstrip_index = drop_index; | |
1792 | |
1793 if (drop_before) { | |
1794 params.disposition = NEW_FOREGROUND_TAB; | |
1795 } else { | |
1796 params.disposition = CURRENT_TAB; | |
1797 params.source_contents = model_->GetWebContentsAt(drop_index); | |
1798 } | |
1799 | |
1800 chrome::Navigate(¶ms); | |
1801 | |
1802 return true; | |
1803 } | |
1804 | |
1805 // static | |
1806 gfx::Image* TabStripGtk::GetDropArrowImage(bool is_down) { | |
1807 return &ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( | |
1808 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); | |
1809 } | |
1810 | |
1811 // TabStripGtk::DropInfo ------------------------------------------------------- | |
1812 | |
1813 TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before, | |
1814 bool point_down) | |
1815 : drop_index(drop_index), | |
1816 drop_before(drop_before), | |
1817 point_down(point_down) { | |
1818 CreateContainer(); | |
1819 drop_arrow = GetDropArrowImage(point_down); | |
1820 } | |
1821 | |
1822 TabStripGtk::DropInfo::~DropInfo() { | |
1823 DestroyContainer(); | |
1824 } | |
1825 | |
1826 gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget, | |
1827 GdkEventExpose* event) { | |
1828 TRACE_EVENT0("ui::gtk", "TabStripGtk::DropInfo::OnExposeEvent"); | |
1829 | |
1830 if (ui::IsScreenComposited()) { | |
1831 SetContainerTransparency(); | |
1832 } else { | |
1833 SetContainerShapeMask(); | |
1834 } | |
1835 | |
1836 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); | |
1837 gdk_cairo_rectangle(cr, &event->area); | |
1838 cairo_clip(cr); | |
1839 | |
1840 drop_arrow->ToCairo()->SetSource(cr, widget, 0, 0); | |
1841 cairo_paint(cr); | |
1842 | |
1843 cairo_destroy(cr); | |
1844 | |
1845 return FALSE; | |
1846 } | |
1847 | |
1848 // Sets the color map of the container window to allow the window to be | |
1849 // transparent. | |
1850 void TabStripGtk::DropInfo::SetContainerColorMap() { | |
1851 GdkScreen* screen = gtk_widget_get_screen(container); | |
1852 GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); | |
1853 | |
1854 // If rgba is not available, use rgb instead. | |
1855 if (!colormap) | |
1856 colormap = gdk_screen_get_rgb_colormap(screen); | |
1857 | |
1858 gtk_widget_set_colormap(container, colormap); | |
1859 } | |
1860 | |
1861 // Sets full transparency for the container window. This is used if | |
1862 // compositing is available for the screen. | |
1863 void TabStripGtk::DropInfo::SetContainerTransparency() { | |
1864 cairo_t* cairo_context = gdk_cairo_create(gtk_widget_get_window(container)); | |
1865 if (!cairo_context) | |
1866 return; | |
1867 | |
1868 // Make the background of the dragged tab window fully transparent. All of | |
1869 // the content of the window (child widgets) will be completely opaque. | |
1870 | |
1871 cairo_scale(cairo_context, static_cast<double>(drop_indicator_width), | |
1872 static_cast<double>(drop_indicator_height)); | |
1873 cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f); | |
1874 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); | |
1875 cairo_paint(cairo_context); | |
1876 cairo_destroy(cairo_context); | |
1877 } | |
1878 | |
1879 // Sets the shape mask for the container window to emulate a transparent | |
1880 // container window. This is used if compositing is not available for the | |
1881 // screen. | |
1882 void TabStripGtk::DropInfo::SetContainerShapeMask() { | |
1883 // Create a 1bpp bitmap the size of |container|. | |
1884 GdkPixmap* pixmap = gdk_pixmap_new(NULL, | |
1885 drop_indicator_width, | |
1886 drop_indicator_height, 1); | |
1887 cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap)); | |
1888 | |
1889 // Set the transparency. | |
1890 cairo_set_source_rgba(cairo_context, 1, 1, 1, 0); | |
1891 | |
1892 // Blit the rendered bitmap into a pixmap. Any pixel set in the pixmap will | |
1893 // be opaque in the container window. | |
1894 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); | |
1895 // We don't use CairoCachedSurface::SetSource() here because we're not | |
1896 // rendering on a display server. | |
1897 gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow->ToGdkPixbuf(), 0, 0); | |
1898 cairo_paint(cairo_context); | |
1899 cairo_destroy(cairo_context); | |
1900 | |
1901 // Set the shape mask. | |
1902 GdkWindow* gdk_window = gtk_widget_get_window(container); | |
1903 gdk_window_shape_combine_mask(gdk_window, pixmap, 0, 0); | |
1904 g_object_unref(pixmap); | |
1905 } | |
1906 | |
1907 void TabStripGtk::DropInfo::CreateContainer() { | |
1908 container = gtk_window_new(GTK_WINDOW_POPUP); | |
1909 SetContainerColorMap(); | |
1910 gtk_widget_set_app_paintable(container, TRUE); | |
1911 g_signal_connect(container, "expose-event", | |
1912 G_CALLBACK(OnExposeEventThunk), this); | |
1913 gtk_widget_add_events(container, GDK_STRUCTURE_MASK); | |
1914 gtk_window_move(GTK_WINDOW(container), 0, 0); | |
1915 gtk_window_resize(GTK_WINDOW(container), | |
1916 drop_indicator_width, drop_indicator_height); | |
1917 gtk_widget_show_all(container); | |
1918 } | |
1919 | |
1920 void TabStripGtk::DropInfo::DestroyContainer() { | |
1921 if (GTK_IS_WIDGET(container)) | |
1922 gtk_widget_destroy(container); | |
1923 } | |
1924 | |
1925 void TabStripGtk::StopAnimation() { | |
1926 if (active_animation_.get()) | |
1927 active_animation_->Stop(); | |
1928 } | |
1929 | |
1930 // Called from: | |
1931 // - animation tick | |
1932 void TabStripGtk::AnimationLayout(double unselected_width) { | |
1933 int tab_height = TabGtk::GetStandardSize().height(); | |
1934 double tab_x = tab_start_x(); | |
1935 for (int i = 0; i < GetTabCount(); ++i) { | |
1936 TabAnimation* animation = active_animation_.get(); | |
1937 if (animation) | |
1938 tab_x += animation->GetGapWidth(i); | |
1939 double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i); | |
1940 double end_of_tab = tab_x + tab_width; | |
1941 int rounded_tab_x = Round(tab_x); | |
1942 TabGtk* tab = GetTabAt(i); | |
1943 gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, | |
1944 tab_height); | |
1945 SetTabBounds(tab, bounds); | |
1946 tab_x = end_of_tab + GetTabHOffset(i + 1); | |
1947 } | |
1948 LayoutNewTabButton(tab_x, unselected_width); | |
1949 } | |
1950 | |
1951 void TabStripGtk::StartInsertTabAnimation(int index) { | |
1952 // The TabStrip can now use its entire width to lay out Tabs. | |
1953 available_width_for_tabs_ = -1; | |
1954 StopAnimation(); | |
1955 active_animation_.reset(new InsertTabAnimation(this, index)); | |
1956 active_animation_->Start(); | |
1957 } | |
1958 | |
1959 void TabStripGtk::StartRemoveTabAnimation(int index, WebContents* contents) { | |
1960 if (active_animation_.get()) { | |
1961 // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when | |
1962 // they're completed (which includes canceled). Since |tab_data_| is now | |
1963 // inconsistent with TabStripModel, doing this Layout will crash now, so | |
1964 // we ask the MoveTabAnimation to skip its Layout (the state will be | |
1965 // corrected by the RemoveTabAnimation we're about to initiate). | |
1966 active_animation_->set_layout_on_completion(false); | |
1967 active_animation_->Stop(); | |
1968 } | |
1969 | |
1970 active_animation_.reset(new RemoveTabAnimation(this, index, contents)); | |
1971 active_animation_->Start(); | |
1972 } | |
1973 | |
1974 void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) { | |
1975 StopAnimation(); | |
1976 active_animation_.reset(new MoveTabAnimation(this, from_index, to_index)); | |
1977 active_animation_->Start(); | |
1978 } | |
1979 | |
1980 void TabStripGtk::StartResizeLayoutAnimation() { | |
1981 StopAnimation(); | |
1982 active_animation_.reset(new ResizeLayoutAnimation(this)); | |
1983 active_animation_->Start(); | |
1984 } | |
1985 | |
1986 void TabStripGtk::StartMiniTabAnimation(int index) { | |
1987 StopAnimation(); | |
1988 active_animation_.reset(new MiniTabAnimation(this, index)); | |
1989 active_animation_->Start(); | |
1990 } | |
1991 | |
1992 void TabStripGtk::StartMiniMoveTabAnimation(int from_index, | |
1993 int to_index, | |
1994 const gfx::Rect& start_bounds) { | |
1995 StopAnimation(); | |
1996 active_animation_.reset( | |
1997 new MiniMoveAnimation(this, from_index, to_index, start_bounds)); | |
1998 active_animation_->Start(); | |
1999 } | |
2000 | |
2001 void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation, | |
2002 bool layout) { | |
2003 active_animation_.reset(NULL); | |
2004 | |
2005 // Reset the animation state of each tab. | |
2006 for (int i = 0, count = GetTabCount(); i < count; ++i) | |
2007 GetTabAt(i)->set_animating_mini_change(false); | |
2008 | |
2009 if (layout) | |
2010 Layout(); | |
2011 } | |
2012 | |
2013 void TabStripGtk::OnMap(GtkWidget* widget) { | |
2014 ReStack(); | |
2015 } | |
2016 | |
2017 gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) { | |
2018 TRACE_EVENT0("ui::gtk", "TabStripGtk::OnExpose"); | |
2019 | |
2020 if (gdk_region_empty(event->region)) | |
2021 return TRUE; | |
2022 | |
2023 // If we're only repainting favicons, optimize the paint path and only draw | |
2024 // the favicons. | |
2025 GdkRectangle* rects; | |
2026 gint num_rects; | |
2027 gdk_region_get_rectangles(event->region, &rects, &num_rects); | |
2028 qsort(rects, num_rects, sizeof(GdkRectangle), CompareGdkRectangles); | |
2029 std::vector<int> tabs_to_repaint; | |
2030 if (!IsDragSessionActive() && | |
2031 CanPaintOnlyFavicons(rects, num_rects, &tabs_to_repaint)) { | |
2032 PaintOnlyFavicons(event, tabs_to_repaint); | |
2033 g_free(rects); | |
2034 return TRUE; | |
2035 } | |
2036 g_free(rects); | |
2037 | |
2038 // Ideally we'd like to only draw what's needed in the damage rect, but the | |
2039 // tab widgets overlap each other. To get the proper visual look, we need to | |
2040 // draw tabs from the rightmost to the leftmost tab. So if we have a dirty | |
2041 // rectangle in the center of the tabstrip, we'll have to draw all the tabs | |
2042 // to the left of it. | |
2043 // | |
2044 // TODO(erg): Figure out why we can't have clip rects that don't start from | |
2045 // x=0. jhawkins had a big comment here about how painting on one widget will | |
2046 // cause an expose-event to be sent to the widgets underneath, but that | |
2047 // should still obey clip rects, but doesn't seem to. | |
2048 if (active_animation_.get() || drag_controller_.get()) { | |
2049 // If we have an active animation or the tab is being dragged, no matter | |
2050 // what GTK tells us our dirty rectangles are, we need to redraw the entire | |
2051 // tabstrip. | |
2052 event->area.width = bounds_.width(); | |
2053 } else { | |
2054 // Expand whatever dirty rectangle we were given to the area from the | |
2055 // leftmost edge of the tabstrip to the rightmost edge of the dirty | |
2056 // rectangle given. | |
2057 // | |
2058 // Doing this frees up CPU when redrawing the tabstrip with throbbing | |
2059 // tabs. The most likely tabs to throb are pinned or minitabs which live on | |
2060 // the very leftmost of the tabstrip. | |
2061 event->area.width += event->area.x; | |
2062 } | |
2063 | |
2064 event->area.x = 0; | |
2065 event->area.y = 0; | |
2066 event->area.height = bounds_.height(); | |
2067 gdk_region_union_with_rect(event->region, &event->area); | |
2068 | |
2069 // Paint the New Tab button. | |
2070 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
2071 newtab_button_->widget(), event); | |
2072 | |
2073 // Paint the tabs in reverse order, so they stack to the left. | |
2074 TabGtk* selected_tab = NULL; | |
2075 int tab_count = GetTabCount(); | |
2076 for (int i = tab_count - 1; i >= 0; --i) { | |
2077 TabGtk* tab = GetTabAt(i); | |
2078 // We must ask the _Tab's_ model, not ourselves, because in some situations | |
2079 // the model will be different to this object, e.g. when a Tab is being | |
2080 // removed after its WebContents has been destroyed. | |
2081 if (!tab->IsActive()) { | |
2082 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
2083 tab->widget(), event); | |
2084 } else { | |
2085 selected_tab = tab; | |
2086 } | |
2087 } | |
2088 | |
2089 // Paint the selected tab last, so it overlaps all the others. | |
2090 if (selected_tab) { | |
2091 gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()), | |
2092 selected_tab->widget(), event); | |
2093 } | |
2094 | |
2095 return TRUE; | |
2096 } | |
2097 | |
2098 void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { | |
2099 TRACE_EVENT0("ui::gtk", "TabStripGtk::OnSizeAllocate"); | |
2100 | |
2101 gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y, | |
2102 allocation->width, allocation->height); | |
2103 | |
2104 // Nothing to do if the bounds are the same. If we don't catch this, we'll | |
2105 // get an infinite loop of size-allocate signals. | |
2106 if (bounds_ == bounds) | |
2107 return; | |
2108 | |
2109 SetBounds(bounds); | |
2110 | |
2111 // No tabs, nothing to layout. This happens when a browser window is created | |
2112 // and shown before tabs are added (as in a popup window). | |
2113 if (GetTabCount() == 0) | |
2114 return; | |
2115 | |
2116 // When there is only one tab, Layout() so we don't animate it. With more | |
2117 // tabs, we should always attempt a resize unless we already have one coming | |
2118 // up in our message loop. | |
2119 if (GetTabCount() == 1) { | |
2120 Layout(); | |
2121 } else if (!layout_factory_.HasWeakPtrs()) { | |
2122 base::MessageLoop::current()->PostDelayedTask( | |
2123 FROM_HERE, | |
2124 base::Bind(&TabStripGtk::Layout, layout_factory_.GetWeakPtr()), | |
2125 base::TimeDelta::FromMilliseconds(kLayoutAfterSizeAllocateMs)); | |
2126 } | |
2127 } | |
2128 | |
2129 gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context, | |
2130 gint x, gint y, guint time) { | |
2131 UpdateDropIndex(context, x, y); | |
2132 return TRUE; | |
2133 } | |
2134 | |
2135 gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context, | |
2136 gint x, gint y, guint time) { | |
2137 if (!drop_info_.get()) | |
2138 return FALSE; | |
2139 | |
2140 GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL); | |
2141 if (target != GDK_NONE) | |
2142 gtk_drag_finish(context, FALSE, FALSE, time); | |
2143 else | |
2144 gtk_drag_get_data(widget, context, target, time); | |
2145 | |
2146 return TRUE; | |
2147 } | |
2148 | |
2149 gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context, | |
2150 guint time) { | |
2151 // Destroy the drop indicator. | |
2152 drop_info_->DestroyContainer(); | |
2153 | |
2154 // Cancel any pending tab transition. | |
2155 hover_tab_selector_.CancelTabTransition(); | |
2156 | |
2157 return FALSE; | |
2158 } | |
2159 | |
2160 gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget, | |
2161 GdkDragContext* context, | |
2162 gint x, gint y, | |
2163 GtkSelectionData* data, | |
2164 guint info, guint time) { | |
2165 bool success = false; | |
2166 | |
2167 if (info == ui::TEXT_URI_LIST || | |
2168 info == ui::NETSCAPE_URL || | |
2169 info == ui::TEXT_PLAIN) { | |
2170 success = CompleteDrop(gtk_selection_data_get_data(data), | |
2171 info == ui::TEXT_PLAIN); | |
2172 } | |
2173 | |
2174 gtk_drag_finish(context, success, FALSE, time); | |
2175 return TRUE; | |
2176 } | |
2177 | |
2178 void TabStripGtk::OnNewTabClicked(GtkWidget* widget) { | |
2179 GdkEvent* event = gtk_get_current_event(); | |
2180 DCHECK_EQ(event->type, GDK_BUTTON_RELEASE); | |
2181 int mouse_button = event->button.button; | |
2182 gdk_event_free(event); | |
2183 | |
2184 switch (mouse_button) { | |
2185 case 1: | |
2186 content::RecordAction(UserMetricsAction("NewTab_Button")); | |
2187 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON, | |
2188 TabStripModel::NEW_TAB_ENUM_COUNT); | |
2189 model_->delegate()->AddTabAt(GURL(), -1, true); | |
2190 break; | |
2191 case 2: { | |
2192 // On middle-click, try to parse the PRIMARY selection as a URL and load | |
2193 // it instead of creating a blank page. | |
2194 GURL url; | |
2195 if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url)) | |
2196 return; | |
2197 | |
2198 Browser* browser = window_->browser(); | |
2199 DCHECK(browser); | |
2200 chrome::AddSelectedTabWithURL( | |
2201 browser, url, content::PAGE_TRANSITION_TYPED); | |
2202 break; | |
2203 } | |
2204 default: | |
2205 NOTREACHED() << "Got click on new tab button with unhandled mouse " | |
2206 << "button " << mouse_button; | |
2207 } | |
2208 } | |
2209 | |
2210 void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) { | |
2211 gfx::Rect bds = bounds; | |
2212 bds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds)); | |
2213 tab->SetBounds(bds); | |
2214 gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(), | |
2215 bds.x(), bds.y()); | |
2216 } | |
2217 | |
2218 bool TabStripGtk::CanPaintOnlyFavicons(const GdkRectangle* rects, | |
2219 int num_rects, std::vector<int>* tabs_to_paint) { | |
2220 // |rects| are sorted so we just need to scan from left to right and compare | |
2221 // it to the tab favicon positions from left to right. | |
2222 int t = 0; | |
2223 for (int r = 0; r < num_rects; ++r) { | |
2224 while (t < GetTabCount()) { | |
2225 TabGtk* tab = GetTabAt(t); | |
2226 if (GdkRectMatchesTabFaviconBounds(rects[r], tab) && | |
2227 tab->ShouldShowIcon()) { | |
2228 tabs_to_paint->push_back(t); | |
2229 ++t; | |
2230 break; | |
2231 } | |
2232 ++t; | |
2233 } | |
2234 } | |
2235 return static_cast<int>(tabs_to_paint->size()) == num_rects; | |
2236 } | |
2237 | |
2238 void TabStripGtk::PaintOnlyFavicons(GdkEventExpose* event, | |
2239 const std::vector<int>& tabs_to_paint) { | |
2240 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(event->window)); | |
2241 for (size_t i = 0; i < tabs_to_paint.size(); ++i) { | |
2242 cairo_save(cr); | |
2243 GetTabAt(tabs_to_paint[i])->PaintFaviconArea(tabstrip_.get(), cr); | |
2244 cairo_restore(cr); | |
2245 } | |
2246 | |
2247 cairo_destroy(cr); | |
2248 } | |
2249 | |
2250 CustomDrawButton* TabStripGtk::MakeNewTabButton() { | |
2251 CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON, | |
2252 IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0); | |
2253 | |
2254 gtk_widget_set_tooltip_text(button->widget(), | |
2255 l10n_util::GetStringUTF8(IDS_TOOLTIP_NEW_TAB).c_str()); | |
2256 | |
2257 // Let the middle mouse button initiate clicks as well. | |
2258 gtk_util::SetButtonTriggersNavigation(button->widget()); | |
2259 g_signal_connect(button->widget(), "clicked", | |
2260 G_CALLBACK(OnNewTabClickedThunk), this); | |
2261 gtk_widget_set_can_focus(button->widget(), FALSE); | |
2262 gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0); | |
2263 | |
2264 return button; | |
2265 } | |
2266 | |
2267 void TabStripGtk::SetNewTabButtonBackground() { | |
2268 SkColor color = theme_service_->GetColor( | |
2269 ThemeProperties::COLOR_BUTTON_BACKGROUND); | |
2270 SkBitmap background = theme_service_->GetImageNamed( | |
2271 IDR_THEME_WINDOW_CONTROL_BACKGROUND).AsBitmap(); | |
2272 SkBitmap mask = theme_service_->GetImageNamed( | |
2273 IDR_NEWTAB_BUTTON_MASK).AsBitmap(); | |
2274 newtab_button_->SetBackground(color, background, mask); | |
2275 } | |
OLD | NEW |