Home | History | Annotate | Download | only in bubble
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/views/bubble/border_contents.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "chrome/browser/ui/window_sizer.h"
     11 #include "third_party/skia/include/core/SkPaint.h"
     12 
     13 void BorderContents::Init() {
     14   // Default arrow location.
     15   BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_LEFT;
     16   if (base::i18n::IsRTL())
     17     arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
     18   DCHECK(!bubble_border_);
     19 
     20   bubble_border_ = new BubbleBorder(arrow_location);
     21   set_border(bubble_border_);
     22   set_background(new BubbleBackground(bubble_border_));
     23 }
     24 
     25 void BorderContents::SetBackgroundColor(SkColor color) {
     26   bubble_border_->set_background_color(color);
     27 }
     28 
     29 void BorderContents::SizeAndGetBounds(
     30     const gfx::Rect& position_relative_to,
     31     BubbleBorder::ArrowLocation arrow_location,
     32     bool allow_bubble_offscreen,
     33     const gfx::Size& contents_size,
     34     gfx::Rect* contents_bounds,
     35     gfx::Rect* window_bounds) {
     36   if (base::i18n::IsRTL())
     37     arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
     38   bubble_border_->set_arrow_location(arrow_location);
     39   // Set the border.
     40   set_border(bubble_border_);
     41 
     42   // Give the contents a margin.
     43   gfx::Size local_contents_size(contents_size);
     44   local_contents_size.Enlarge(kLeftMargin + kRightMargin,
     45                               kTopMargin + kBottomMargin);
     46 
     47   // Try putting the arrow in its initial location, and calculating the bounds.
     48   *window_bounds =
     49       bubble_border_->GetBounds(position_relative_to, local_contents_size);
     50   if (!allow_bubble_offscreen) {
     51     gfx::Rect monitor_bounds = GetMonitorBounds(position_relative_to);
     52     if (!monitor_bounds.IsEmpty()) {
     53       // Try to resize vertically if this does not fit on the screen.
     54       MirrorArrowIfOffScreen(true,  // |vertical|.
     55                              position_relative_to, monitor_bounds,
     56                              local_contents_size, &arrow_location,
     57                              window_bounds);
     58       // Then try to resize horizontally if it still does not fit on the screen.
     59       MirrorArrowIfOffScreen(false,  // |vertical|.
     60                              position_relative_to, monitor_bounds,
     61                              local_contents_size, &arrow_location,
     62                              window_bounds);
     63     }
     64   }
     65 
     66   // Calculate the bounds of the contained contents (in window coordinates) by
     67   // subtracting the border dimensions and margin amounts.
     68   *contents_bounds = gfx::Rect(gfx::Point(), window_bounds->size());
     69   gfx::Insets insets;
     70   bubble_border_->GetInsets(&insets);
     71   contents_bounds->Inset(insets.left() + kLeftMargin, insets.top() + kTopMargin,
     72       insets.right() + kRightMargin, insets.bottom() + kBottomMargin);
     73 }
     74 
     75 gfx::Rect BorderContents::GetMonitorBounds(const gfx::Rect& rect) {
     76   scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_provider(
     77       WindowSizer::CreateDefaultMonitorInfoProvider());
     78   return monitor_provider->GetMonitorWorkAreaMatching(rect);
     79 }
     80 
     81 void BorderContents::MirrorArrowIfOffScreen(
     82     bool vertical,
     83     const gfx::Rect& position_relative_to,
     84     const gfx::Rect& monitor_bounds,
     85     const gfx::Size& local_contents_size,
     86     BubbleBorder::ArrowLocation* arrow_location,
     87     gfx::Rect* window_bounds) {
     88   // If the bounds don't fit, move the arrow to its mirrored position to see if
     89   // it improves things.
     90   gfx::Insets offscreen_insets;
     91   if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
     92                              &offscreen_insets) &&
     93       GetInsetsLength(offscreen_insets, vertical) > 0) {
     94     BubbleBorder::ArrowLocation original_arrow_location = *arrow_location;
     95     *arrow_location =
     96         vertical ? BubbleBorder::vertical_mirror(*arrow_location) :
     97                    BubbleBorder::horizontal_mirror(*arrow_location);
     98 
     99     // Change the arrow and get the new bounds.
    100     bubble_border_->set_arrow_location(*arrow_location);
    101     *window_bounds = bubble_border_->GetBounds(position_relative_to,
    102                                                local_contents_size);
    103     gfx::Insets new_offscreen_insets;
    104     // If there is more of the window offscreen, we'll keep the old arrow.
    105     if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
    106                                &new_offscreen_insets) &&
    107         GetInsetsLength(new_offscreen_insets, vertical) >=
    108             GetInsetsLength(offscreen_insets, vertical)) {
    109       *arrow_location = original_arrow_location;
    110       bubble_border_->set_arrow_location(*arrow_location);
    111       *window_bounds = bubble_border_->GetBounds(position_relative_to,
    112                                                  local_contents_size);
    113     }
    114   }
    115 }
    116 
    117 // static
    118 bool BorderContents::ComputeOffScreenInsets(const gfx::Rect& monitor_bounds,
    119                                             const gfx::Rect& window_bounds,
    120                                             gfx::Insets* offscreen_insets) {
    121   if (monitor_bounds.Contains(window_bounds))
    122     return false;
    123 
    124   if (!offscreen_insets)
    125     return true;
    126 
    127   //  window_bounds
    128   //  +-------------------------------+
    129   //  |             top               |
    130   //  |      +----------------+       |
    131   //  | left | monitor_bounds | right |
    132   //  |      +----------------+       |
    133   //  |            bottom             |
    134   //  +-------------------------------+
    135   int top = std::max(0, monitor_bounds.y() - window_bounds.y());
    136   int left = std::max(0, monitor_bounds.x() - window_bounds.x());
    137   int bottom = std::max(0, window_bounds.bottom() - monitor_bounds.bottom());
    138   int right = std::max(0, window_bounds.right() - monitor_bounds.right());
    139 
    140   offscreen_insets->Set(top, left, bottom, right);
    141   return true;
    142 }
    143 
    144 // static
    145 int BorderContents::GetInsetsLength(const gfx::Insets& insets, bool vertical) {
    146   return vertical ? insets.height() : insets.width();
    147 }
    148