OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/views/tabs/tab.h" | 5 #include "chrome/browser/ui/views/tabs/tab.h" |
6 | 6 |
7 #include <limits> | 7 #include <limits> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/debug/alias.h" | 10 #include "base/debug/alias.h" |
(...skipping 27 matching lines...) Expand all Loading... |
38 #include "ui/gfx/favicon_size.h" | 38 #include "ui/gfx/favicon_size.h" |
39 #include "ui/gfx/image/image_skia_operations.h" | 39 #include "ui/gfx/image/image_skia_operations.h" |
40 #include "ui/gfx/path.h" | 40 #include "ui/gfx/path.h" |
41 #include "ui/gfx/rect_conversions.h" | 41 #include "ui/gfx/rect_conversions.h" |
42 #include "ui/gfx/skia_util.h" | 42 #include "ui/gfx/skia_util.h" |
43 #include "ui/gfx/text_elider.h" | 43 #include "ui/gfx/text_elider.h" |
44 #include "ui/views/border.h" | 44 #include "ui/views/border.h" |
45 #include "ui/views/controls/button/image_button.h" | 45 #include "ui/views/controls/button/image_button.h" |
46 #include "ui/views/controls/label.h" | 46 #include "ui/views/controls/label.h" |
47 #include "ui/views/rect_based_targeting_utils.h" | 47 #include "ui/views/rect_based_targeting_utils.h" |
| 48 #include "ui/views/view_targeter.h" |
48 #include "ui/views/widget/tooltip_manager.h" | 49 #include "ui/views/widget/tooltip_manager.h" |
49 #include "ui/views/widget/widget.h" | 50 #include "ui/views/widget/widget.h" |
50 #include "ui/views/window/non_client_view.h" | 51 #include "ui/views/window/non_client_view.h" |
51 | 52 |
52 namespace { | 53 namespace { |
53 | 54 |
54 // Padding around the "content" of a tab, occupied by the tab border graphics. | 55 // Padding around the "content" of a tab, occupied by the tab border graphics. |
55 const int kLeftPadding = 22; | 56 const int kLeftPadding = 22; |
56 const int kTopPadding = 7; | 57 const int kTopPadding = 7; |
57 const int kRightPadding = 17; | 58 const int kRightPadding = 17; |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
224 Tab* target_; | 225 Tab* target_; |
225 | 226 |
226 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation); | 227 DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation); |
227 }; | 228 }; |
228 | 229 |
229 //////////////////////////////////////////////////////////////////////////////// | 230 //////////////////////////////////////////////////////////////////////////////// |
230 // TabCloseButton | 231 // TabCloseButton |
231 // | 232 // |
232 // This is a Button subclass that causes middle clicks to be forwarded to the | 233 // This is a Button subclass that causes middle clicks to be forwarded to the |
233 // parent View by explicitly not handling them in OnMousePressed. | 234 // parent View by explicitly not handling them in OnMousePressed. |
234 class Tab::TabCloseButton : public views::ImageButton { | 235 class Tab::TabCloseButton : public views::ImageButton, |
| 236 public views::MaskedTargeterDelegate { |
235 public: | 237 public: |
236 explicit TabCloseButton(Tab* tab) : views::ImageButton(tab), tab_(tab) {} | 238 explicit TabCloseButton(Tab* tab) |
| 239 : views::ImageButton(tab), |
| 240 tab_(tab) { |
| 241 SetEventTargeter( |
| 242 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); |
| 243 } |
| 244 |
237 virtual ~TabCloseButton() {} | 245 virtual ~TabCloseButton() {} |
238 | 246 |
239 // Overridden from views::View. | 247 // views::View: |
240 virtual View* GetEventHandlerForRect(const gfx::Rect& rect) OVERRIDE { | 248 virtual View* GetEventHandlerForRect(const gfx::Rect& rect) OVERRIDE { |
241 if (!views::UsePointBasedTargeting(rect)) | 249 if (!views::UsePointBasedTargeting(rect)) |
242 return View::GetEventHandlerForRect(rect); | 250 return View::GetEventHandlerForRect(rect); |
243 | 251 |
244 // Ignore the padding set on the button. | 252 // Ignore the padding set on the button. |
245 gfx::Rect contents_bounds = GetContentsBounds(); | 253 gfx::Rect contents_bounds = GetContentsBounds(); |
246 contents_bounds.set_x(GetMirroredXForRect(contents_bounds)); | 254 contents_bounds.set_x(GetMirroredXForRect(contents_bounds)); |
247 | 255 |
248 // Include the padding in hit-test for touch events. | 256 // Include the padding in hit-test for touch events. |
249 if (aura::Env::GetInstance()->is_touch_down()) | 257 if (aura::Env::GetInstance()->is_touch_down()) |
250 contents_bounds = GetLocalBounds(); | 258 contents_bounds = GetLocalBounds(); |
251 | 259 |
252 return contents_bounds.Intersects(rect) ? this : parent(); | 260 return contents_bounds.Intersects(rect) ? this : parent(); |
253 } | 261 } |
254 | 262 |
255 // Overridden from views::View. | |
256 virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE { | 263 virtual View* GetTooltipHandlerForPoint(const gfx::Point& point) OVERRIDE { |
257 // Tab close button has no children, so tooltip handler should be the same | 264 // Tab close button has no children, so tooltip handler should be the same |
258 // as the event handler. | 265 // as the event handler. |
259 // In addition, a hit test has to be performed for the point (as | 266 // In addition, a hit test has to be performed for the point (as |
260 // GetTooltipHandlerForPoint() is responsible for it). | 267 // GetTooltipHandlerForPoint() is responsible for it). |
261 if (!HitTestPoint(point)) | 268 if (!HitTestPoint(point)) |
262 return NULL; | 269 return NULL; |
263 return GetEventHandlerForPoint(point); | 270 return GetEventHandlerForPoint(point); |
264 } | 271 } |
265 | 272 |
(...skipping 16 matching lines...) Expand all Loading... |
282 CustomButton::OnMouseReleased(event); | 289 CustomButton::OnMouseReleased(event); |
283 } | 290 } |
284 | 291 |
285 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { | 292 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { |
286 // Consume all gesture events here so that the parent (Tab) does not | 293 // Consume all gesture events here so that the parent (Tab) does not |
287 // start consuming gestures. | 294 // start consuming gestures. |
288 ImageButton::OnGestureEvent(event); | 295 ImageButton::OnGestureEvent(event); |
289 event->SetHandled(); | 296 event->SetHandled(); |
290 } | 297 } |
291 | 298 |
292 virtual bool HasHitTestMask() const OVERRIDE { | 299 virtual const char* GetClassName() const OVERRIDE { |
293 return true; | 300 return kTabCloseButtonName; |
294 } | 301 } |
295 | 302 |
296 virtual void GetHitTestMaskDeprecated(HitTestSource source, | 303 private: |
297 gfx::Path* path) const OVERRIDE { | 304 // Returns the rectangular bounds of parent tab's visible region in the |
298 // Use the button's contents bounds (which does not include padding) | 305 // local coordinate space of |this|. |
299 // and the hit test mask of our parent |tab_| to determine if the | 306 gfx::Rect GetTabBounds() const { |
300 // button is hidden behind another tab. | |
301 gfx::Path tab_mask; | 307 gfx::Path tab_mask; |
302 tab_->GetHitTestMaskDeprecated(source, &tab_mask); | 308 tab_->GetHitTestMask(&tab_mask); |
303 | 309 |
| 310 gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds())); |
| 311 views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f); |
| 312 return gfx::ToEnclosingRect(tab_bounds_f); |
| 313 } |
| 314 |
| 315 // Returns the rectangular bounds of the tab close button in the local |
| 316 // coordinate space of |this|, not including clipped regions on the top |
| 317 // or bottom of the button. |tab_bounds| is the rectangular bounds of |
| 318 // the parent tab's visible region in the local coordinate space of |this|. |
| 319 gfx::Rect GetTabCloseButtonBounds(const gfx::Rect& tab_bounds) const { |
304 gfx::Rect button_bounds(GetContentsBounds()); | 320 gfx::Rect button_bounds(GetContentsBounds()); |
305 button_bounds.set_x(GetMirroredXForRect(button_bounds)); | 321 button_bounds.set_x(GetMirroredXForRect(button_bounds)); |
306 gfx::RectF tab_bounds_f(gfx::SkRectToRectF(tab_mask.getBounds())); | |
307 views::View::ConvertRectToTarget(tab_, this, &tab_bounds_f); | |
308 gfx::Rect tab_bounds = gfx::ToEnclosingRect(tab_bounds_f); | |
309 | 322 |
310 // If either the top or bottom of the tab close button is clipped, | |
311 // do not consider these regions to be part of the button's bounds. | |
312 int top_overflow = tab_bounds.y() - button_bounds.y(); | 323 int top_overflow = tab_bounds.y() - button_bounds.y(); |
313 int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom(); | 324 int bottom_overflow = button_bounds.bottom() - tab_bounds.bottom(); |
314 if (top_overflow > 0) | 325 if (top_overflow > 0) |
315 button_bounds.set_y(tab_bounds.y()); | 326 button_bounds.set_y(tab_bounds.y()); |
316 else if (bottom_overflow > 0) | 327 else if (bottom_overflow > 0) |
317 button_bounds.set_height(button_bounds.height() - bottom_overflow); | 328 button_bounds.set_height(button_bounds.height() - bottom_overflow); |
318 | 329 |
319 // If the hit test request is in response to a gesture, |path| should be | 330 return button_bounds; |
320 // empty unless the entire tab close button is visible to the user. Hit | |
321 // test requests in response to a mouse event should always set |path| | |
322 // to be the visible portion of the tab close button, even if it is | |
323 // partially hidden behind another tab. | |
324 path->reset(); | |
325 gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds)); | |
326 if (!intersection.IsEmpty()) { | |
327 // TODO(tdanderson): Consider always returning the intersection if | |
328 // the non-rectangular shape of the tabs can be accounted for. | |
329 if (source == HIT_TEST_SOURCE_TOUCH && | |
330 !tab_bounds.Contains(button_bounds)) | |
331 return; | |
332 | |
333 path->addRect(RectToSkRect(intersection)); | |
334 } | |
335 } | 331 } |
336 | 332 |
337 virtual const char* GetClassName() const OVERRIDE { | 333 // views:MaskedTargeterDelegate: |
338 return kTabCloseButtonName; | 334 virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE { |
| 335 DCHECK(mask); |
| 336 mask->reset(); |
| 337 |
| 338 // The parent tab may be partially occluded by another tab if we are |
| 339 // in stacked tab mode, which means that the tab close button may also |
| 340 // be partially occluded. Define the hit test mask of the tab close |
| 341 // button to be the intersection of the parent tab's visible bounds |
| 342 // and the bounds of the tab close button. |
| 343 gfx::Rect tab_bounds(GetTabBounds()); |
| 344 gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds)); |
| 345 gfx::Rect intersection(gfx::IntersectRects(tab_bounds, button_bounds)); |
| 346 |
| 347 if (!intersection.IsEmpty()) { |
| 348 mask->addRect(RectToSkRect(intersection)); |
| 349 return true; |
| 350 } |
| 351 |
| 352 return false; |
339 } | 353 } |
340 | 354 |
341 private: | 355 virtual bool DoesIntersectRect(const View* target, |
| 356 const gfx::Rect& rect) const OVERRIDE { |
| 357 CHECK_EQ(target, this); |
| 358 |
| 359 // If the request is not made in response to a gesture, use the |
| 360 // default implementation. |
| 361 if (views::UsePointBasedTargeting(rect)) |
| 362 return MaskedTargeterDelegate::DoesIntersectRect(target, rect); |
| 363 |
| 364 // The hit test request is in response to a gesture. Return false if any |
| 365 // part of the tab close button is hidden from the user. |
| 366 // TODO(tdanderson): Consider always returning the intersection if the |
| 367 // non-rectangular shape of the tab can be accounted for. |
| 368 gfx::Rect tab_bounds(GetTabBounds()); |
| 369 gfx::Rect button_bounds(GetTabCloseButtonBounds(tab_bounds)); |
| 370 if (!tab_bounds.Contains(button_bounds)) |
| 371 return false; |
| 372 |
| 373 return MaskedTargeterDelegate::DoesIntersectRect(target, rect); |
| 374 } |
| 375 |
342 Tab* tab_; | 376 Tab* tab_; |
343 | 377 |
344 DISALLOW_COPY_AND_ASSIGN(TabCloseButton); | 378 DISALLOW_COPY_AND_ASSIGN(TabCloseButton); |
345 }; | 379 }; |
346 | 380 |
347 //////////////////////////////////////////////////////////////////////////////// | 381 //////////////////////////////////////////////////////////////////////////////// |
348 // ImageCacheEntry | 382 // ImageCacheEntry |
349 | 383 |
350 Tab::ImageCacheEntry::ImageCacheEntry() | 384 Tab::ImageCacheEntry::ImageCacheEntry() |
351 : resource_id(-1), | 385 : resource_id(-1), |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
394 | 428 |
395 set_id(VIEW_ID_TAB); | 429 set_id(VIEW_ID_TAB); |
396 | 430 |
397 title_->set_directionality_mode(gfx::DIRECTIONALITY_FROM_TEXT); | 431 title_->set_directionality_mode(gfx::DIRECTIONALITY_FROM_TEXT); |
398 title_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); | 432 title_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); |
399 title_->SetElideBehavior(gfx::FADE_TAIL); | 433 title_->SetElideBehavior(gfx::FADE_TAIL); |
400 title_->SetAutoColorReadabilityEnabled(false); | 434 title_->SetAutoColorReadabilityEnabled(false); |
401 title_->SetText(CoreTabHelper::GetDefaultTitle()); | 435 title_->SetText(CoreTabHelper::GetDefaultTitle()); |
402 AddChildView(title_); | 436 AddChildView(title_); |
403 | 437 |
| 438 SetEventTargeter( |
| 439 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); |
| 440 |
404 // Add the Close Button. | 441 // Add the Close Button. |
405 close_button_ = new TabCloseButton(this); | 442 close_button_ = new TabCloseButton(this); |
406 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 443 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
407 close_button_->SetImage(views::CustomButton::STATE_NORMAL, | 444 close_button_->SetImage(views::CustomButton::STATE_NORMAL, |
408 rb.GetImageSkiaNamed(IDR_CLOSE_1)); | 445 rb.GetImageSkiaNamed(IDR_CLOSE_1)); |
409 close_button_->SetImage(views::CustomButton::STATE_HOVERED, | 446 close_button_->SetImage(views::CustomButton::STATE_HOVERED, |
410 rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); | 447 rb.GetImageSkiaNamed(IDR_CLOSE_1_H)); |
411 close_button_->SetImage(views::CustomButton::STATE_PRESSED, | 448 close_button_->SetImage(views::CustomButton::STATE_PRESSED, |
412 rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); | 449 rb.GetImageSkiaNamed(IDR_CLOSE_1_P)); |
413 close_button_->SetAccessibleName( | 450 close_button_->SetAccessibleName( |
(...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
648 // Tab, views::ContextMenuController overrides: | 685 // Tab, views::ContextMenuController overrides: |
649 | 686 |
650 void Tab::ShowContextMenuForView(views::View* source, | 687 void Tab::ShowContextMenuForView(views::View* source, |
651 const gfx::Point& point, | 688 const gfx::Point& point, |
652 ui::MenuSourceType source_type) { | 689 ui::MenuSourceType source_type) { |
653 if (!closing()) | 690 if (!closing()) |
654 controller_->ShowContextMenuForTab(this, point, source_type); | 691 controller_->ShowContextMenuForTab(this, point, source_type); |
655 } | 692 } |
656 | 693 |
657 //////////////////////////////////////////////////////////////////////////////// | 694 //////////////////////////////////////////////////////////////////////////////// |
| 695 // Tab, views::MaskedTargeterDelegate overrides: |
| 696 |
| 697 bool Tab::GetHitTestMask(gfx::Path* mask) const { |
| 698 DCHECK(mask); |
| 699 |
| 700 // When the window is maximized we don't want to shave off the edges or top |
| 701 // shadow of the tab, such that the user can click anywhere along the top |
| 702 // edge of the screen to select a tab. Ditto for immersive fullscreen. |
| 703 const views::Widget* widget = GetWidget(); |
| 704 bool include_top_shadow = |
| 705 widget && (widget->IsMaximized() || widget->IsFullscreen()); |
| 706 TabResources::GetHitTestMask(width(), height(), include_top_shadow, mask); |
| 707 |
| 708 // It is possible for a portion of the tab to be occluded if tabs are |
| 709 // stacked, so modify the hit test mask to only include the visible |
| 710 // region of the tab. |
| 711 gfx::Rect clip; |
| 712 controller_->ShouldPaintTab(this, &clip); |
| 713 if (clip.size().GetArea()) { |
| 714 SkRect intersection(mask->getBounds()); |
| 715 intersection.intersect(RectToSkRect(clip)); |
| 716 mask->reset(); |
| 717 mask->addRect(intersection); |
| 718 } |
| 719 |
| 720 return true; |
| 721 } |
| 722 |
| 723 //////////////////////////////////////////////////////////////////////////////// |
658 // Tab, views::View overrides: | 724 // Tab, views::View overrides: |
659 | 725 |
660 void Tab::OnPaint(gfx::Canvas* canvas) { | 726 void Tab::OnPaint(gfx::Canvas* canvas) { |
661 // Don't paint if we're narrower than we can render correctly. (This should | 727 // Don't paint if we're narrower than we can render correctly. (This should |
662 // only happen during animations). | 728 // only happen during animations). |
663 if (width() < GetMinimumUnselectedSize().width() && !data().mini) | 729 if (width() < GetMinimumUnselectedSize().width() && !data().mini) |
664 return; | 730 return; |
665 | 731 |
666 gfx::Rect clip; | 732 gfx::Rect clip; |
667 if (!controller_->ShouldPaintTab(this, &clip)) | 733 if (!controller_->ShouldPaintTab(this, &clip)) |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
796 } | 862 } |
797 | 863 |
798 void Tab::OnThemeChanged() { | 864 void Tab::OnThemeChanged() { |
799 LoadTabImages(); | 865 LoadTabImages(); |
800 } | 866 } |
801 | 867 |
802 const char* Tab::GetClassName() const { | 868 const char* Tab::GetClassName() const { |
803 return kViewClassName; | 869 return kViewClassName; |
804 } | 870 } |
805 | 871 |
806 bool Tab::HasHitTestMask() const { | |
807 return true; | |
808 } | |
809 | |
810 void Tab::GetHitTestMaskDeprecated(HitTestSource source, | |
811 gfx::Path* path) const { | |
812 // When the window is maximized we don't want to shave off the edges or top | |
813 // shadow of the tab, such that the user can click anywhere along the top | |
814 // edge of the screen to select a tab. Ditto for immersive fullscreen. | |
815 const views::Widget* widget = GetWidget(); | |
816 bool include_top_shadow = | |
817 widget && (widget->IsMaximized() || widget->IsFullscreen()); | |
818 TabResources::GetHitTestMask(width(), height(), include_top_shadow, path); | |
819 | |
820 // It is possible for a portion of the tab to be occluded if tabs are | |
821 // stacked, so modify the hit test mask to only include the visible | |
822 // region of the tab. | |
823 gfx::Rect clip; | |
824 controller_->ShouldPaintTab(this, &clip); | |
825 if (clip.size().GetArea()) { | |
826 SkRect intersection(path->getBounds()); | |
827 intersection.intersect(RectToSkRect(clip)); | |
828 path->reset(); | |
829 path->addRect(intersection); | |
830 } | |
831 } | |
832 | |
833 bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { | 872 bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { |
834 // Note: Anything that affects the tooltip text should be accounted for when | 873 // Note: Anything that affects the tooltip text should be accounted for when |
835 // calling TooltipTextChanged() from Tab::DataChanged(). | 874 // calling TooltipTextChanged() from Tab::DataChanged(). |
836 *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state); | 875 *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.media_state); |
837 return !tooltip->empty(); | 876 return !tooltip->empty(); |
838 } | 877 } |
839 | 878 |
840 bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const { | 879 bool Tab::GetTooltipTextOrigin(const gfx::Point& p, gfx::Point* origin) const { |
841 origin->set_x(title_->x() + 10); | 880 origin->set_x(title_->x() + 10); |
842 origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); | 881 origin->set_y(-views::TooltipManager::GetTooltipHeight() - 4); |
(...skipping 772 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1615 const gfx::ImageSkia& image) { | 1654 const gfx::ImageSkia& image) { |
1616 DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE); | 1655 DCHECK_NE(scale_factor, ui::SCALE_FACTOR_NONE); |
1617 ImageCacheEntry entry; | 1656 ImageCacheEntry entry; |
1618 entry.resource_id = resource_id; | 1657 entry.resource_id = resource_id; |
1619 entry.scale_factor = scale_factor; | 1658 entry.scale_factor = scale_factor; |
1620 entry.image = image; | 1659 entry.image = image; |
1621 image_cache_->push_front(entry); | 1660 image_cache_->push_front(entry); |
1622 if (image_cache_->size() > kMaxImageCacheSize) | 1661 if (image_cache_->size() > kMaxImageCacheSize) |
1623 image_cache_->pop_back(); | 1662 image_cache_->pop_back(); |
1624 } | 1663 } |
OLD | NEW |