// Copyright (c) 2006-2008 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 <atlbase.h>
#include <atlapp.h>

#include "chrome/views/menu_button.h"

#include "chrome/app/theme/theme_resources.h"
#include "chrome/common/drag_drop_types.h"
#include "chrome/common/gfx/chrome_canvas.h"
#include "chrome/common/l10n_util.h"
#include "chrome/common/resource_bundle.h"
#include "chrome/common/win_util.h"
#include "chrome/views/button.h"
#include "chrome/views/event.h"
#include "chrome/views/root_view.h"
#include "chrome/views/view_menu_delegate.h"
#include "chrome/views/widget.h"

#include "generated_resources.h"

using base::Time;
using base::TimeDelta;

namespace views {

// The amount of time, in milliseconds, we wait before allowing another mouse
// pressed event to show the menu.
static const int64 kMinimumTimeBetweenButtonClicks = 100;

// The down arrow used to differentiate the menu button from normal
// text buttons.
static const SkBitmap* kMenuMarker = NULL;

// How much padding to put on the left and right of the menu marker.
static const int kMenuMarkerPaddingLeft = 3;
static const int kMenuMarkerPaddingRight = -1;

////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - constructors, destructors, initialization
//
////////////////////////////////////////////////////////////////////////////////

MenuButton::MenuButton(const std::wstring& text,
                       ViewMenuDelegate* menu_delegate,
                       bool show_menu_marker)
    : TextButton(text),
      menu_visible_(false),
      menu_closed_time_(),
      menu_delegate_(menu_delegate),
      show_menu_marker_(show_menu_marker) {
  if (kMenuMarker == NULL) {
    kMenuMarker = ResourceBundle::GetSharedInstance()
        .GetBitmapNamed(IDR_MENU_DROPARROW);
  }
  SetTextAlignment(TextButton::ALIGN_LEFT);
}

MenuButton::~MenuButton() {
}

////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - Public APIs
//
////////////////////////////////////////////////////////////////////////////////

gfx::Size MenuButton::GetPreferredSize() {
  gfx::Size prefsize = TextButton::GetPreferredSize();
  if (show_menu_marker_) {
    prefsize.Enlarge(kMenuMarker->width() + kMenuMarkerPaddingLeft +
                         kMenuMarkerPaddingRight,
                     0);
  }
  return prefsize;
}

void MenuButton::Paint(ChromeCanvas* canvas, bool for_drag) {
  TextButton::Paint(canvas, for_drag);

  if (show_menu_marker_) {
    gfx::Insets insets = GetInsets();

    // We can not use the views' mirroring infrastructure for mirroring a
    // MenuButton control (see TextButton::Paint() for a detailed explanation
    // regarding why we can not flip the canvas). Therefore, we need to
    // manually mirror the position of the down arrow.
    gfx::Rect arrow_bounds(width() - insets.right() -
                           kMenuMarker->width() - kMenuMarkerPaddingRight,
                           height() / 2 - kMenuMarker->height() / 2,
                           kMenuMarker->width(),
                           kMenuMarker->height());
    arrow_bounds.set_x(MirroredLeftPointForRect(arrow_bounds));
    canvas->DrawBitmapInt(*kMenuMarker, arrow_bounds.x(), arrow_bounds.y());
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - Events
//
////////////////////////////////////////////////////////////////////////////////

int MenuButton::GetMaximumScreenXCoordinate() {
  Widget* widget = GetWidget();

  if (!widget) {
    NOTREACHED();
    return 0;
  }

  HWND hwnd = widget->GetHWND();
  CRect t;
  ::GetWindowRect(hwnd, &t);

  gfx::Rect r(t);
  gfx::Rect monitor_rect = win_util::GetMonitorBoundsForRect(r);
  return monitor_rect.x() + monitor_rect.width() - 1;
}

bool MenuButton::Activate() {
  SetState(BS_PUSHED);
  // We need to synchronously paint here because subsequently we enter a
  // menu modal loop which will stop this window from updating and
  // receiving the paint message that should be spawned by SetState until
  // after the menu closes.
  PaintNow();
  if (menu_delegate_) {
    gfx::Rect lb = GetLocalBounds(true);

    // The position of the menu depends on whether or not the locale is
    // right-to-left.
    gfx::Point menu_position(lb.right(), lb.bottom());
    if (UILayoutIsRightToLeft())
      menu_position.set_x(lb.x());

    View::ConvertPointToScreen(this, &menu_position);
    if (UILayoutIsRightToLeft())
      menu_position.Offset(2, -4);
    else
      menu_position.Offset(-2, -4);

    int max_x_coordinate = GetMaximumScreenXCoordinate();
    if (max_x_coordinate && max_x_coordinate <= menu_position.x())
      menu_position.set_x(max_x_coordinate - 1);

    // We're about to show the menu from a mouse press. By showing from the
    // mouse press event we block RootView in mouse dispatching. This also
    // appears to cause RootView to get a mouse pressed BEFORE the mouse
    // release is seen, which means RootView sends us another mouse press no
    // matter where the user pressed. To force RootView to recalculate the
    // mouse target during the mouse press we explicitly set the mouse handler
    // to NULL.
    GetRootView()->SetMouseHandler(NULL);

    menu_visible_ = true;
    menu_delegate_->RunMenu(this, menu_position.ToPOINT(),
                            GetWidget()->GetHWND());
    menu_visible_ = false;
    menu_closed_time_ = Time::Now();

    // Now that the menu has closed, we need to manually reset state to
    // "normal" since the menu modal loop will have prevented normal
    // mouse move messages from getting to this View. We set "normal"
    // and not "hot" because the likelihood is that the mouse is now
    // somewhere else (user clicked elsewhere on screen to close the menu
    // or selected an item) and we will inevitably refresh the hot state
    // in the event the mouse _is_ over the view.
    SetState(BS_NORMAL);

    // We must return false here so that the RootView does not get stuck
    // sending all mouse pressed events to us instead of the appropriate
    // target.
    return false;
  }
  return true;
}

bool MenuButton::OnMousePressed(const MouseEvent& e) {
  if (IsFocusable())
    RequestFocus();
  if (GetState() != BS_DISABLED) {
    // If we're draggable (GetDragOperations returns a non-zero value), then
    // don't pop on press, instead wait for release.
    if (e.IsOnlyLeftMouseButton() && HitTest(e.location()) &&
        GetDragOperations(e.x(), e.y()) == DragDropTypes::DRAG_NONE) {
      TimeDelta delta = Time::Now() - menu_closed_time_;
      int64 delta_in_milliseconds = delta.InMilliseconds();
      if (delta_in_milliseconds > kMinimumTimeBetweenButtonClicks) {
        return Activate();
      }
    }
  }
  return true;
}

void MenuButton::OnMouseReleased(const MouseEvent& e,
                                 bool canceled) {
  if (GetDragOperations(e.x(), e.y()) != DragDropTypes::DRAG_NONE &&
      GetState() != BS_DISABLED && !canceled && !InDrag() &&
      e.IsOnlyLeftMouseButton() && HitTest(e.location())) {
    Activate();
  } else {
    TextButton::OnMouseReleased(e, canceled);
  }
}

// When the space bar or the enter key is pressed we need to show the menu.
bool MenuButton::OnKeyReleased(const KeyEvent& e) {
  if ((e.GetCharacter() == VK_SPACE) || (e.GetCharacter() == VK_RETURN)) {
    return Activate();
  }
  return true;
}

// The reason we override View::OnMouseExited is because we get this event when
// we display the menu. If we don't override this method then
// BaseButton::OnMouseExited will get the event and will set the button's state
// to BS_NORMAL instead of keeping the state BM_PUSHED. This, in turn, will
// cause the button to appear depressed while the menu is displayed.
void MenuButton::OnMouseExited(const MouseEvent& event) {
  if ((state_ != BS_DISABLED) && (!menu_visible_) && (!InDrag())) {
    SetState(BS_NORMAL);
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - accessibility
//
////////////////////////////////////////////////////////////////////////////////

bool MenuButton::GetAccessibleDefaultAction(std::wstring* action) {
  DCHECK(action);

  action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS));
  return true;
}

bool MenuButton::GetAccessibleRole(VARIANT* role) {
  DCHECK(role);

  role->vt = VT_I4;
  role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN;
  return true;
}

bool MenuButton::GetAccessibleState(VARIANT* state) {
  DCHECK(state);

  state->lVal |= STATE_SYSTEM_HASPOPUP;
  return true;
}

}  // namespace views