text: // 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/infobars/infobar.h"
  
  #include <cmath>
  
  #include "base/logging.h"
  #include "build/build_config.h"
  #include "chrome/browser/infobars/infobar_container.h"
  #include "chrome/browser/infobars/infobar_service.h"
  #include "ui/base/animation/slide_animation.h"
  
  SkColor GetInfoBarTopColor(InfoBarDelegate::Type infobar_type) {
    static const SkColor kWarningBackgroundColorTop =
        SkColorSetRGB(255, 242, 183);  // Yellow
    static const SkColor kPageActionBackgroundColorTop =
        SkColorSetRGB(237, 237, 237);  // Gray
    return (infobar_type == InfoBarDelegate::WARNING_TYPE) ?
        kWarningBackgroundColorTop : kPageActionBackgroundColorTop;
  }
  
  SkColor GetInfoBarBottomColor(InfoBarDelegate::Type infobar_type) {
    static const SkColor kWarningBackgroundColorBottom =
        SkColorSetRGB(250, 230, 145);  // Yellow
    static const SkColor kPageActionBackgroundColorBottom =
        SkColorSetRGB(217, 217, 217);  // Gray
    return (infobar_type == InfoBarDelegate::WARNING_TYPE) ?
        kWarningBackgroundColorBottom : kPageActionBackgroundColorBottom;
  }
  
  // TODO(pkasting): Port Mac to use this.
  #if defined(TOOLKIT_VIEWS) || defined(TOOLKIT_GTK) || defined(OS_ANDROID)
  
  InfoBar::InfoBar(InfoBarService* owner, InfoBarDelegate* delegate)
      : owner_(owner),
        delegate_(delegate),
        container_(NULL),
        animation_(this),
        arrow_height_(0),
        arrow_target_height_(kDefaultArrowTargetHeight),
        arrow_half_width_(0),
        bar_height_(0),
        bar_target_height_(kDefaultBarTargetHeight) {
    DCHECK(owner_ != NULL);
    DCHECK(delegate_ != NULL);
    animation_.SetTweenType(ui::Tween::LINEAR);
  }
  
  InfoBar::~InfoBar() {
  }
  
  void InfoBar::Show(bool animate) {
    PlatformSpecificShow(animate);
    if (animate) {
      animation_.Show();
    } else {
      animation_.Reset(1.0);
      RecalculateHeights(true);
    }
  }
  
  void InfoBar::Hide(bool animate) {
    PlatformSpecificHide(animate);
    if (animate) {
      animation_.Hide();
    } else {
      animation_.Reset(0.0);
      // We want to remove ourselves from the container immediately even if we
      // still have an owner, which MaybeDelete() won't do.
      DCHECK(container_);
      container_->RemoveInfoBar(this);
      MaybeDelete();  // Necessary if the infobar was already closing.
    }
  }
  
  void InfoBar::SetArrowTargetHeight(int height) {
    DCHECK_LE(height, kMaximumArrowTargetHeight);
    // Once the closing animation starts, we ignore further requests to change the
    // target height.
    if ((arrow_target_height_ != height) && !animation_.IsClosing()) {
      arrow_target_height_ = height;
      RecalculateHeights(false);
    }
  }
  
  void InfoBar::CloseSoon() {
    owner_ = NULL;
    PlatformSpecificOnCloseSoon();
    MaybeDelete();
  }
  
  void InfoBar::AnimationProgressed(const ui::Animation* animation) {
    RecalculateHeights(false);
  }
  
  void InfoBar::RemoveSelf() {
    // |owner_| should never be NULL here.  If it is, then someone violated what
    // they were supposed to do -- e.g. a ConfirmInfoBarDelegate subclass returned
    // true from Accept() or Cancel() even though the infobar was already closing.
    // In the worst case, if we also switched tabs during that process, then
    // |this| has already been destroyed.  But if that's the case, then we're
    // going to deref a garbage |this| pointer here whether we check |owner_| or
    // not, and in other cases (where we're still closing and |this| is valid),
    // checking |owner_| here will avoid a NULL deref.
    if (owner_)
      owner_->RemoveInfoBar(delegate_);
  }
  
  void InfoBar::SetBarTargetHeight(int height) {
    if (bar_target_height_ != height) {
      bar_target_height_ = height;
      RecalculateHeights(false);
    }
  }
  
  int InfoBar::OffsetY(const gfx::Size& prefsize) const {
    return arrow_height_ +
        std::max((bar_target_height_ - prefsize.height()) / 2, 0) -
        (bar_target_height_ - bar_height_);
  }
  
  void InfoBar::AnimationEnded(const ui::Animation* animation) {
    // When the animation ends, we must ensure the container is notified even if
    // the heights haven't changed, lest it never get an "animation finished"
    // notification.  (If the browser doesn't get this notification, it will not
    // bother to re-layout the content area for the new infobar size.)
    RecalculateHeights(true);
    MaybeDelete();
  }
  
  void InfoBar::RecalculateHeights(bool force_notify) {
    int old_arrow_height = arrow_height_;
    int old_bar_height = bar_height_;
  
    // Find the desired arrow height/half-width.  The arrow area is
    // |arrow_height_| * |arrow_half_width_|.  When the bar is opening or closing,
    // scaling each of these with the square root of the animation value causes a
    // linear animation of the area, which matches the perception of the animation
    // of the bar portion.
    double scale_factor = sqrt(animation_.GetCurrentValue());
    arrow_height_ = static_cast<int>(arrow_target_height_ * scale_factor);
    if (animation_.is_animating()) {
      arrow_half_width_ = static_cast<int>(std::min(arrow_target_height_,
          kMaximumArrowTargetHalfWidth) * scale_factor);
    } else {
      // When the infobar is not animating (i.e. fully open), we set the
      // half-width to be proportionally the same distance between its default and
      // maximum values as the height is between its.
      arrow_half_width_ = kDefaultArrowTargetHalfWidth +
          ((kMaximumArrowTargetHalfWidth - kDefaultArrowTargetHalfWidth) *
           ((arrow_height_ - kDefaultArrowTargetHeight) /
            (kMaximumArrowTargetHeight - kDefaultArrowTargetHeight)));
    }
    // Add pixels for the stroke, if the arrow is to be visible at all.  Without
    // this, changing the arrow height from 0 to kSeparatorLineHeight would
    // produce no visible effect, because the stroke would paint atop the divider
    // line above the infobar.
    if (arrow_height_)
      arrow_height_ += kSeparatorLineHeight;
  
    bar_height_ = animation_.CurrentValueBetween(0, bar_target_height_);
  
    // Don't re-layout if nothing has changed, e.g. because the animation step was
    // not large enough to actually change the heights by at least a pixel.
    bool heights_differ =
        (old_arrow_height != arrow_height_) || (old_bar_height != bar_height_);
    if (heights_differ)
      PlatformSpecificOnHeightsRecalculated();
  
    if (container_ && (heights_differ || force_notify))
      container_->OnInfoBarStateChanged(animation_.is_animating());
  }
  
  void InfoBar::MaybeDelete() {
    if (!owner_ && delegate_ && (animation_.GetCurrentValue() == 0.0)) {
      if (container_)
        container_->RemoveInfoBar(this);
      delete delegate_;
      delegate_ = NULL;
    }
  }
  
  #endif  // TOOLKIT_VIEWS || TOOLKIT_GTK || OS_ANDROID
  