// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/bubble/border_contents.h"
#include <algorithm>
#include "base/logging.h"
#include "chrome/browser/ui/window_sizer.h"
#include "third_party/skia/include/core/SkPaint.h"
void BorderContents::Init() {
// Default arrow location.
BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_LEFT;
if (base::i18n::IsRTL())
arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
DCHECK(!bubble_border_);
bubble_border_ = new BubbleBorder(arrow_location);
set_border(bubble_border_);
set_background(new BubbleBackground(bubble_border_));
}
void BorderContents::SetBackgroundColor(SkColor color) {
bubble_border_->set_background_color(color);
}
void BorderContents::SizeAndGetBounds(
const gfx::Rect& position_relative_to,
BubbleBorder::ArrowLocation arrow_location,
bool allow_bubble_offscreen,
const gfx::Size& contents_size,
gfx::Rect* contents_bounds,
gfx::Rect* window_bounds) {
if (base::i18n::IsRTL())
arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
bubble_border_->set_arrow_location(arrow_location);
// Set the border.
set_border(bubble_border_);
// Give the contents a margin.
gfx::Size local_contents_size(contents_size);
local_contents_size.Enlarge(kLeftMargin + kRightMargin,
kTopMargin + kBottomMargin);
// Try putting the arrow in its initial location, and calculating the bounds.
*window_bounds =
bubble_border_->GetBounds(position_relative_to, local_contents_size);
if (!allow_bubble_offscreen) {
gfx::Rect monitor_bounds = GetMonitorBounds(position_relative_to);
if (!monitor_bounds.IsEmpty()) {
// Try to resize vertically if this does not fit on the screen.
MirrorArrowIfOffScreen(true, // |vertical|.
position_relative_to, monitor_bounds,
local_contents_size, &arrow_location,
window_bounds);
// Then try to resize horizontally if it still does not fit on the screen.
MirrorArrowIfOffScreen(false, // |vertical|.
position_relative_to, monitor_bounds,
local_contents_size, &arrow_location,
window_bounds);
}
}
// Calculate the bounds of the contained contents (in window coordinates) by
// subtracting the border dimensions and margin amounts.
*contents_bounds = gfx::Rect(gfx::Point(), window_bounds->size());
gfx::Insets insets;
bubble_border_->GetInsets(&insets);
contents_bounds->Inset(insets.left() + kLeftMargin, insets.top() + kTopMargin,
insets.right() + kRightMargin, insets.bottom() + kBottomMargin);
}
gfx::Rect BorderContents::GetMonitorBounds(const gfx::Rect& rect) {
scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_provider(
WindowSizer::CreateDefaultMonitorInfoProvider());
return monitor_provider->GetMonitorWorkAreaMatching(rect);
}
void BorderContents::MirrorArrowIfOffScreen(
bool vertical,
const gfx::Rect& position_relative_to,
const gfx::Rect& monitor_bounds,
const gfx::Size& local_contents_size,
BubbleBorder::ArrowLocation* arrow_location,
gfx::Rect* window_bounds) {
// If the bounds don't fit, move the arrow to its mirrored position to see if
// it improves things.
gfx::Insets offscreen_insets;
if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
&offscreen_insets) &&
GetInsetsLength(offscreen_insets, vertical) > 0) {
BubbleBorder::ArrowLocation original_arrow_location = *arrow_location;
*arrow_location =
vertical ? BubbleBorder::vertical_mirror(*arrow_location) :
BubbleBorder::horizontal_mirror(*arrow_location);
// Change the arrow and get the new bounds.
bubble_border_->set_arrow_location(*arrow_location);
*window_bounds = bubble_border_->GetBounds(position_relative_to,
local_contents_size);
gfx::Insets new_offscreen_insets;
// If there is more of the window offscreen, we'll keep the old arrow.
if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
&new_offscreen_insets) &&
GetInsetsLength(new_offscreen_insets, vertical) >=
GetInsetsLength(offscreen_insets, vertical)) {
*arrow_location = original_arrow_location;
bubble_border_->set_arrow_location(*arrow_location);
*window_bounds = bubble_border_->GetBounds(position_relative_to,
local_contents_size);
}
}
}
// static
bool BorderContents::ComputeOffScreenInsets(const gfx::Rect& monitor_bounds,
const gfx::Rect& window_bounds,
gfx::Insets* offscreen_insets) {
if (monitor_bounds.Contains(window_bounds))
return false;
if (!offscreen_insets)
return true;
// window_bounds
// +-------------------------------+
// | top |
// | +----------------+ |
// | left | monitor_bounds | right |
// | +----------------+ |
// | bottom |
// +-------------------------------+
int top = std::max(0, monitor_bounds.y() - window_bounds.y());
int left = std::max(0, monitor_bounds.x() - window_bounds.x());
int bottom = std::max(0, window_bounds.bottom() - monitor_bounds.bottom());
int right = std::max(0, window_bounds.right() - monitor_bounds.right());
offscreen_insets->Set(top, left, bottom, right);
return true;
}
// static
int BorderContents::GetInsetsLength(const gfx::Insets& insets, bool vertical) {
return vertical ? insets.height() : insets.width();
}