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
13void 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
25void BorderContents::SetBackgroundColor(SkColor color) {
26  bubble_border_->set_background_color(color);
27}
28
29void 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
75gfx::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
81void 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
118bool 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
145int BorderContents::GetInsetsLength(const gfx::Insets& insets, bool vertical) {
146  return vertical ? insets.height() : insets.width();
147}
148