// 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/chromeos/frame/browser_view.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/chromeos/frame/panel_browser_view.h"
#include "chrome/browser/chromeos/status/input_method_menu_button.h"
#include "chrome/browser/chromeos/status/network_menu_button.h"
#include "chrome/browser/chromeos/status/status_area_button.h"
#include "chrome/browser/chromeos/status/status_area_view.h"
#include "chrome/browser/chromeos/view_ids.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/views/frame/browser_frame_gtk.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/browser_view_layout.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/theme_background.h"
#include "chrome/browser/ui/views/toolbar_view.h"
#include "chrome/common/chrome_switches.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "third_party/cros/chromeos_wm_ipc_enums.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/canvas.h"
#include "views/controls/button/button.h"
#include "views/controls/button/image_button.h"
#include "views/controls/menu/menu_2.h"
#include "views/screen.h"
#include "views/widget/root_view.h"
#include "views/window/hit_test.h"
#include "views/window/window.h"

namespace {

// Amount to offset the toolbar by when vertical tabs are enabled.
const int kVerticalTabStripToolbarOffset = 2;
// Amount to tweak the position of the status area to get it to look right.
const int kStatusAreaVerticalAdjustment = -1;

// If a popup window is larger than this fraction of the screen, create a tab.
const float kPopupMaxWidthFactor = 0.5;
const float kPopupMaxHeightFactor = 0.6;

}  // namespace

namespace chromeos {

// LayoutManager for BrowserView, which layouts extra components such as
// the status views as follows:
//       ____  __ __
//      /    \   \  \     [StatusArea]
//
class BrowserViewLayout : public ::BrowserViewLayout {
 public:
  BrowserViewLayout() : ::BrowserViewLayout() {}
  virtual ~BrowserViewLayout() {}

  //////////////////////////////////////////////////////////////////////////////
  // BrowserViewLayout overrides:

  void Installed(views::View* host) {
    status_area_ = NULL;
    ::BrowserViewLayout::Installed(host);
  }

  void ViewAdded(views::View* host,
                 views::View* view) {
    ::BrowserViewLayout::ViewAdded(host, view);
    switch (view->GetID()) {
      case VIEW_ID_STATUS_AREA:
        status_area_ = static_cast<chromeos::StatusAreaView*>(view);
        break;
    }
  }

  // In the normal and the compact navigation bar mode, ChromeOS
  // layouts compact navigation buttons and status views in the title
  // area. See Layout
  virtual int LayoutTabStrip() {
    if (browser_view_->IsFullscreen() || !browser_view_->IsTabStripVisible()) {
      status_area_->SetVisible(false);
      tabstrip_->SetVisible(false);
      tabstrip_->SetBounds(0, 0, 0, 0);
      return 0;
    }

    gfx::Rect tabstrip_bounds(
        browser_view_->frame()->GetBoundsForTabStrip(tabstrip_));
    gfx::Point tabstrip_origin = tabstrip_bounds.origin();
    views::View::ConvertPointToView(browser_view_->parent(), browser_view_,
                                    &tabstrip_origin);
    tabstrip_bounds.set_origin(tabstrip_origin);
    return browser_view_->UseVerticalTabs() ?
        LayoutTitlebarComponentsWithVerticalTabs(tabstrip_bounds) :
        LayoutTitlebarComponents(tabstrip_bounds);
  }

  virtual int LayoutToolbar(int top) {
    if (!browser_view_->IsFullscreen() && browser_view_->IsTabStripVisible() &&
        browser_view_->UseVerticalTabs()) {
      // For vertical tabs the toolbar is positioned in
      // LayoutTitlebarComponentsWithVerticalTabs.
      return top;
    }
    return ::BrowserViewLayout::LayoutToolbar(top);
  }

  virtual bool IsPositionInWindowCaption(const gfx::Point& point) {
    return ::BrowserViewLayout::IsPositionInWindowCaption(point)
        && !IsPointInViewsInTitleArea(point);
  }

  virtual int NonClientHitTest(const gfx::Point& point) {
    gfx::Point point_in_browser_view_coords(point);
    views::View::ConvertPointToView(
        browser_view_->parent(), browser_view_,
        &point_in_browser_view_coords);
    return IsPointInViewsInTitleArea(point_in_browser_view_coords) ?
        HTCLIENT : ::BrowserViewLayout::NonClientHitTest(point);
  }

 private:
  chromeos::BrowserView* chromeos_browser_view() {
    return static_cast<chromeos::BrowserView*>(browser_view_);
  }

  // Tests if the point is on one of views that are within the
  // considered title bar area of client view.
  bool IsPointInViewsInTitleArea(const gfx::Point& point)
      const {
    gfx::Point point_in_status_area_coords(point);
    views::View::ConvertPointToView(browser_view_, status_area_,
                                    &point_in_status_area_coords);
    if (status_area_->HitTest(point_in_status_area_coords))
      return true;

    return false;
  }

  // Positions the titlebar, toolbar and tabstrip. This is
  // used when side tabs are enabled.
  int LayoutTitlebarComponentsWithVerticalTabs(const gfx::Rect& bounds) {
    if (bounds.IsEmpty())
      return 0;

    tabstrip_->SetVisible(true);
    status_area_->SetVisible(true);

    gfx::Size status_size = status_area_->GetPreferredSize();
    int status_height = status_size.height();

    int status_x = bounds.x();
    // Layout the status area.
    status_area_->SetBounds(status_x, bounds.bottom() - status_height,
                            status_size.width(), status_height);

    // The tabstrip's width is the bigger of it's preferred width and the width
    // the status area.
    int tabstrip_w = std::max(status_x + status_size.width(),
                              tabstrip_->GetPreferredSize().width());
    tabstrip_->SetBounds(bounds.x(), bounds.y(), tabstrip_w,
                         bounds.height() - status_height);

    // The toolbar is promoted to the title for vertical tabs.
    bool toolbar_visible = browser_view_->IsToolbarVisible();
    int toolbar_height = 0;
    if (toolbar_) {
      toolbar_->SetVisible(toolbar_visible);
      if (toolbar_visible)
        toolbar_height = toolbar_->GetPreferredSize().height();
      int tabstrip_max_x = tabstrip_->bounds().right();
      toolbar_->SetBounds(tabstrip_max_x,
                          bounds.y() - kVerticalTabStripToolbarOffset,
                          browser_view_->width() - tabstrip_max_x,
                          toolbar_height);
    }
    // Adjust the available bounds for other components.
    gfx::Rect available_bounds = vertical_layout_rect();
    available_bounds.Inset(tabstrip_w, 0, 0, 0);
    set_vertical_layout_rect(available_bounds);
    return bounds.y() + toolbar_height;
  }

  // Lays out tabstrip and status area in the title bar area (given by
  // |bounds|).
  int LayoutTitlebarComponents(const gfx::Rect& bounds) {
    if (bounds.IsEmpty())
      return 0;

    tabstrip_->SetVisible(true);
    status_area_->SetVisible(true);

    // Layout status area after tab strip.
    gfx::Size status_size = status_area_->GetPreferredSize();
    status_area_->SetBounds(
        bounds.right() - status_size.width(),
        bounds.y() + kStatusAreaVerticalAdjustment,
        status_size.width(),
        status_size.height());
    tabstrip_->SetBounds(bounds.x(), bounds.y(),
        std::max(0, status_area_->bounds().x() - bounds.x()),
        bounds.height());
    return bounds.bottom();
  }

  chromeos::StatusAreaView* status_area_;

  DISALLOW_COPY_AND_ASSIGN(BrowserViewLayout);
};

BrowserView::BrowserView(Browser* browser)
    : ::BrowserView(browser),
      status_area_(NULL),
      saved_focused_widget_(NULL) {
}

BrowserView::~BrowserView() {
  if (toolbar())
    toolbar()->RemoveMenuListener(this);
}

////////////////////////////////////////////////////////////////////////////////
// BrowserView, ::BrowserView overrides:

void BrowserView::Init() {
  ::BrowserView::Init();
  status_area_ = new StatusAreaView(this);
  status_area_->SetID(VIEW_ID_STATUS_AREA);
  AddChildView(status_area_);
  status_area_->Init();
  InitSystemMenu();

  // The ContextMenuController has to be set to a NonClientView but
  // not to a NonClientFrameView because a TabStrip is not a child of
  // a NonClientFrameView even though visually a TabStrip is over a
  // NonClientFrameView.
  BrowserFrameGtk* gtk_frame = static_cast<BrowserFrameGtk*>(frame());
  gtk_frame->non_client_view()->SetContextMenuController(this);

  // Listen to wrench menu opens.
  if (toolbar())
    toolbar()->AddMenuListener(this);

  // Make sure the window is set to the right type.
  std::vector<int> params;
  params.push_back(browser()->tab_count());
  params.push_back(browser()->active_index());
  params.push_back(gtk_get_current_event_time());
  WmIpc::instance()->SetWindowType(
      GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()),
      WM_IPC_WINDOW_CHROME_TOPLEVEL,
      &params);
}

void BrowserView::Show() {
  ShowInternal(true);
}

void BrowserView::ShowInactive() {
  ShowInternal(false);
}

void BrowserView::ShowInternal(bool is_active) {
  bool was_visible = frame()->GetWindow()->IsVisible();
  if (is_active)
    ::BrowserView::Show();
  else
    ::BrowserView::ShowInactive();
  if (!was_visible) {
    // Have to update the tab count and selected index to reflect reality.
    std::vector<int> params;
    params.push_back(browser()->tab_count());
    params.push_back(browser()->active_index());
    WmIpc::instance()->SetWindowType(
        GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()),
        WM_IPC_WINDOW_CHROME_TOPLEVEL,
        &params);
  }
}

void BrowserView::FocusChromeOSStatus() {
  SaveFocusedView();
  status_area_->SetPaneFocus(last_focused_view_storage_id(), NULL);
}

views::LayoutManager* BrowserView::CreateLayoutManager() const {
  return new BrowserViewLayout();
}

void BrowserView::ChildPreferredSizeChanged(View* child) {
  Layout();
}

bool BrowserView::GetSavedWindowBounds(gfx::Rect* bounds) const {
  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeosFrame)) {
    // Typically we don't request a full screen size. This means we'll request a
    // non-full screen size, layout/paint at that size, then the window manager
    // will snap us to full screen size. This results in an ugly
    // resize/paint. To avoid this we always request a full screen size.
    *bounds = views::Screen::GetMonitorWorkAreaNearestWindow(
        GTK_WIDGET(GetWindow()->GetNativeWindow()));
    return true;
  }
  return ::BrowserView::GetSavedWindowBounds(bounds);
}

void BrowserView::Cut() {
  gtk_util::DoCut(this);
}

void BrowserView::Copy() {
  gtk_util::DoCopy(this);
}

void BrowserView::Paste() {
  gtk_util::DoPaste(this);
}

// views::ContextMenuController overrides.
void BrowserView::ShowContextMenuForView(views::View* source,
                                         const gfx::Point& p,
                                         bool is_mouse_gesture) {
  // Only show context menu if point is in unobscured parts of browser, i.e.
  // if NonClientHitTest returns :
  // - HTCAPTION: in title bar or unobscured part of tabstrip
  // - HTNOWHERE: as the name implies.
  gfx::Point point_in_parent_coords(p);
  views::View::ConvertPointToView(NULL, parent(), &point_in_parent_coords);
  int hit_test = NonClientHitTest(point_in_parent_coords);
  if (hit_test == HTCAPTION || hit_test == HTNOWHERE)
    system_menu_menu_->RunMenuAt(p, views::Menu2::ALIGN_TOPLEFT);
}

void BrowserView::OnMenuOpened() {
  // Save the focused widget before wrench menu opens.
  saved_focused_widget_ = gtk_window_get_focus(GetNativeHandle());
}

// StatusAreaHost overrides.
Profile* BrowserView::GetProfile() const {
  return browser()->profile();
}

gfx::NativeWindow BrowserView::GetNativeWindow() const {
  return GetWindow()->GetNativeWindow();
}

bool BrowserView::ShouldOpenButtonOptions(
    const views::View* button_view) const {
  return true;
}

void BrowserView::ExecuteBrowserCommand(int id) const {
  browser()->ExecuteCommand(id);
}

void BrowserView::OpenButtonOptions(const views::View* button_view) {
  if (button_view == status_area_->network_view()) {
    browser()->OpenInternetOptionsDialog();
  } else if (button_view == status_area_->input_method_view()) {
    browser()->OpenLanguageOptionsDialog();
  } else {
    browser()->OpenSystemOptionsDialog();
  }
}

StatusAreaHost::ScreenMode BrowserView::GetScreenMode() const {
  return kBrowserMode;
}

StatusAreaHost::TextStyle BrowserView::GetTextStyle() const {
  ui::ThemeProvider* tp = GetThemeProvider();
  return tp->HasCustomImage(IDR_THEME_FRAME) ?
      StatusAreaHost::kWhiteHaloed : (IsOffTheRecord() ?
          StatusAreaHost::kWhitePlain : StatusAreaHost::kGrayEmbossed);
}

////////////////////////////////////////////////////////////////////////////////
// BrowserView protected:

void BrowserView::GetAccessiblePanes(
    std::vector<AccessiblePaneView*>* panes) {
  ::BrowserView::GetAccessiblePanes(panes);
  panes->push_back(status_area_);
}

////////////////////////////////////////////////////////////////////////////////
// BrowserView private:

void BrowserView::InitSystemMenu() {
  system_menu_contents_.reset(new ui::SimpleMenuModel(this));
  system_menu_contents_->AddItemWithStringId(IDC_RESTORE_TAB,
                                               IDS_RESTORE_TAB);
  system_menu_contents_->AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
  system_menu_contents_->AddSeparator();
  system_menu_contents_->AddItemWithStringId(IDC_TASK_MANAGER,
                                               IDS_TASK_MANAGER);
  system_menu_menu_.reset(new views::Menu2(system_menu_contents_.get()));
}

}  // namespace chromeos

// static
BrowserWindow* BrowserWindow::CreateBrowserWindow(Browser* browser) {
  // Create a browser view for chromeos.
  BrowserView* view;
  if (browser->type() & Browser::TYPE_POPUP)
    view = new chromeos::PanelBrowserView(browser);
  else
    view = new chromeos::BrowserView(browser);
  BrowserFrame::Create(view, browser->profile());
  return view;
}