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/chromeos/frame/browser_view.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/command_line.h"
12#include "chrome/app/chrome_command_ids.h"
13#include "chrome/browser/chromeos/frame/panel_browser_view.h"
14#include "chrome/browser/chromeos/status/input_method_menu_button.h"
15#include "chrome/browser/chromeos/status/network_menu_button.h"
16#include "chrome/browser/chromeos/status/status_area_button.h"
17#include "chrome/browser/chromeos/status/status_area_view.h"
18#include "chrome/browser/chromeos/view_ids.h"
19#include "chrome/browser/chromeos/wm_ipc.h"
20#include "chrome/browser/ui/gtk/gtk_util.h"
21#include "chrome/browser/ui/views/frame/browser_frame_gtk.h"
22#include "chrome/browser/ui/views/frame/browser_view.h"
23#include "chrome/browser/ui/views/frame/browser_view_layout.h"
24#include "chrome/browser/ui/views/tabs/tab.h"
25#include "chrome/browser/ui/views/tabs/tab_strip.h"
26#include "chrome/browser/ui/views/theme_background.h"
27#include "chrome/browser/ui/views/toolbar_view.h"
28#include "chrome/common/chrome_switches.h"
29#include "grit/generated_resources.h"
30#include "grit/theme_resources.h"
31#include "third_party/cros/chromeos_wm_ipc_enums.h"
32#include "ui/base/models/simple_menu_model.h"
33#include "ui/base/theme_provider.h"
34#include "ui/gfx/canvas.h"
35#include "views/controls/button/button.h"
36#include "views/controls/button/image_button.h"
37#include "views/controls/menu/menu_2.h"
38#include "views/screen.h"
39#include "views/widget/root_view.h"
40#include "views/window/hit_test.h"
41#include "views/window/window.h"
42
43namespace {
44
45// Amount to offset the toolbar by when vertical tabs are enabled.
46const int kVerticalTabStripToolbarOffset = 2;
47// Amount to tweak the position of the status area to get it to look right.
48const int kStatusAreaVerticalAdjustment = -1;
49
50// If a popup window is larger than this fraction of the screen, create a tab.
51const float kPopupMaxWidthFactor = 0.5;
52const float kPopupMaxHeightFactor = 0.6;
53
54}  // namespace
55
56namespace chromeos {
57
58// LayoutManager for BrowserView, which layouts extra components such as
59// the status views as follows:
60//       ____  __ __
61//      /    \   \  \     [StatusArea]
62//
63class BrowserViewLayout : public ::BrowserViewLayout {
64 public:
65  BrowserViewLayout() : ::BrowserViewLayout() {}
66  virtual ~BrowserViewLayout() {}
67
68  //////////////////////////////////////////////////////////////////////////////
69  // BrowserViewLayout overrides:
70
71  void Installed(views::View* host) {
72    status_area_ = NULL;
73    ::BrowserViewLayout::Installed(host);
74  }
75
76  void ViewAdded(views::View* host,
77                 views::View* view) {
78    ::BrowserViewLayout::ViewAdded(host, view);
79    switch (view->GetID()) {
80      case VIEW_ID_STATUS_AREA:
81        status_area_ = static_cast<chromeos::StatusAreaView*>(view);
82        break;
83    }
84  }
85
86  // In the normal and the compact navigation bar mode, ChromeOS
87  // layouts compact navigation buttons and status views in the title
88  // area. See Layout
89  virtual int LayoutTabStrip() {
90    if (browser_view_->IsFullscreen() || !browser_view_->IsTabStripVisible()) {
91      status_area_->SetVisible(false);
92      tabstrip_->SetVisible(false);
93      tabstrip_->SetBounds(0, 0, 0, 0);
94      return 0;
95    }
96
97    gfx::Rect tabstrip_bounds(
98        browser_view_->frame()->GetBoundsForTabStrip(tabstrip_));
99    gfx::Point tabstrip_origin = tabstrip_bounds.origin();
100    views::View::ConvertPointToView(browser_view_->parent(), browser_view_,
101                                    &tabstrip_origin);
102    tabstrip_bounds.set_origin(tabstrip_origin);
103    return browser_view_->UseVerticalTabs() ?
104        LayoutTitlebarComponentsWithVerticalTabs(tabstrip_bounds) :
105        LayoutTitlebarComponents(tabstrip_bounds);
106  }
107
108  virtual int LayoutToolbar(int top) {
109    if (!browser_view_->IsFullscreen() && browser_view_->IsTabStripVisible() &&
110        browser_view_->UseVerticalTabs()) {
111      // For vertical tabs the toolbar is positioned in
112      // LayoutTitlebarComponentsWithVerticalTabs.
113      return top;
114    }
115    return ::BrowserViewLayout::LayoutToolbar(top);
116  }
117
118  virtual bool IsPositionInWindowCaption(const gfx::Point& point) {
119    return ::BrowserViewLayout::IsPositionInWindowCaption(point)
120        && !IsPointInViewsInTitleArea(point);
121  }
122
123  virtual int NonClientHitTest(const gfx::Point& point) {
124    gfx::Point point_in_browser_view_coords(point);
125    views::View::ConvertPointToView(
126        browser_view_->parent(), browser_view_,
127        &point_in_browser_view_coords);
128    return IsPointInViewsInTitleArea(point_in_browser_view_coords) ?
129        HTCLIENT : ::BrowserViewLayout::NonClientHitTest(point);
130  }
131
132 private:
133  chromeos::BrowserView* chromeos_browser_view() {
134    return static_cast<chromeos::BrowserView*>(browser_view_);
135  }
136
137  // Tests if the point is on one of views that are within the
138  // considered title bar area of client view.
139  bool IsPointInViewsInTitleArea(const gfx::Point& point)
140      const {
141    gfx::Point point_in_status_area_coords(point);
142    views::View::ConvertPointToView(browser_view_, status_area_,
143                                    &point_in_status_area_coords);
144    if (status_area_->HitTest(point_in_status_area_coords))
145      return true;
146
147    return false;
148  }
149
150  // Positions the titlebar, toolbar and tabstrip. This is
151  // used when side tabs are enabled.
152  int LayoutTitlebarComponentsWithVerticalTabs(const gfx::Rect& bounds) {
153    if (bounds.IsEmpty())
154      return 0;
155
156    tabstrip_->SetVisible(true);
157    status_area_->SetVisible(true);
158
159    gfx::Size status_size = status_area_->GetPreferredSize();
160    int status_height = status_size.height();
161
162    int status_x = bounds.x();
163    // Layout the status area.
164    status_area_->SetBounds(status_x, bounds.bottom() - status_height,
165                            status_size.width(), status_height);
166
167    // The tabstrip's width is the bigger of it's preferred width and the width
168    // the status area.
169    int tabstrip_w = std::max(status_x + status_size.width(),
170                              tabstrip_->GetPreferredSize().width());
171    tabstrip_->SetBounds(bounds.x(), bounds.y(), tabstrip_w,
172                         bounds.height() - status_height);
173
174    // The toolbar is promoted to the title for vertical tabs.
175    bool toolbar_visible = browser_view_->IsToolbarVisible();
176    int toolbar_height = 0;
177    if (toolbar_) {
178      toolbar_->SetVisible(toolbar_visible);
179      if (toolbar_visible)
180        toolbar_height = toolbar_->GetPreferredSize().height();
181      int tabstrip_max_x = tabstrip_->bounds().right();
182      toolbar_->SetBounds(tabstrip_max_x,
183                          bounds.y() - kVerticalTabStripToolbarOffset,
184                          browser_view_->width() - tabstrip_max_x,
185                          toolbar_height);
186    }
187    // Adjust the available bounds for other components.
188    gfx::Rect available_bounds = vertical_layout_rect();
189    available_bounds.Inset(tabstrip_w, 0, 0, 0);
190    set_vertical_layout_rect(available_bounds);
191    return bounds.y() + toolbar_height;
192  }
193
194  // Lays out tabstrip and status area in the title bar area (given by
195  // |bounds|).
196  int LayoutTitlebarComponents(const gfx::Rect& bounds) {
197    if (bounds.IsEmpty())
198      return 0;
199
200    tabstrip_->SetVisible(true);
201    status_area_->SetVisible(true);
202
203    // Layout status area after tab strip.
204    gfx::Size status_size = status_area_->GetPreferredSize();
205    status_area_->SetBounds(
206        bounds.right() - status_size.width(),
207        bounds.y() + kStatusAreaVerticalAdjustment,
208        status_size.width(),
209        status_size.height());
210    tabstrip_->SetBounds(bounds.x(), bounds.y(),
211        std::max(0, status_area_->bounds().x() - bounds.x()),
212        bounds.height());
213    return bounds.bottom();
214  }
215
216  chromeos::StatusAreaView* status_area_;
217
218  DISALLOW_COPY_AND_ASSIGN(BrowserViewLayout);
219};
220
221BrowserView::BrowserView(Browser* browser)
222    : ::BrowserView(browser),
223      status_area_(NULL),
224      saved_focused_widget_(NULL) {
225}
226
227BrowserView::~BrowserView() {
228  if (toolbar())
229    toolbar()->RemoveMenuListener(this);
230}
231
232////////////////////////////////////////////////////////////////////////////////
233// BrowserView, ::BrowserView overrides:
234
235void BrowserView::Init() {
236  ::BrowserView::Init();
237  status_area_ = new StatusAreaView(this);
238  status_area_->SetID(VIEW_ID_STATUS_AREA);
239  AddChildView(status_area_);
240  status_area_->Init();
241  InitSystemMenu();
242
243  // The ContextMenuController has to be set to a NonClientView but
244  // not to a NonClientFrameView because a TabStrip is not a child of
245  // a NonClientFrameView even though visually a TabStrip is over a
246  // NonClientFrameView.
247  BrowserFrameGtk* gtk_frame = static_cast<BrowserFrameGtk*>(frame());
248  gtk_frame->non_client_view()->SetContextMenuController(this);
249
250  // Listen to wrench menu opens.
251  if (toolbar())
252    toolbar()->AddMenuListener(this);
253
254  // Make sure the window is set to the right type.
255  std::vector<int> params;
256  params.push_back(browser()->tab_count());
257  params.push_back(browser()->active_index());
258  params.push_back(gtk_get_current_event_time());
259  WmIpc::instance()->SetWindowType(
260      GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()),
261      WM_IPC_WINDOW_CHROME_TOPLEVEL,
262      &params);
263}
264
265void BrowserView::Show() {
266  ShowInternal(true);
267}
268
269void BrowserView::ShowInactive() {
270  ShowInternal(false);
271}
272
273void BrowserView::ShowInternal(bool is_active) {
274  bool was_visible = frame()->GetWindow()->IsVisible();
275  if (is_active)
276    ::BrowserView::Show();
277  else
278    ::BrowserView::ShowInactive();
279  if (!was_visible) {
280    // Have to update the tab count and selected index to reflect reality.
281    std::vector<int> params;
282    params.push_back(browser()->tab_count());
283    params.push_back(browser()->active_index());
284    WmIpc::instance()->SetWindowType(
285        GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()),
286        WM_IPC_WINDOW_CHROME_TOPLEVEL,
287        &params);
288  }
289}
290
291void BrowserView::FocusChromeOSStatus() {
292  SaveFocusedView();
293  status_area_->SetPaneFocus(last_focused_view_storage_id(), NULL);
294}
295
296views::LayoutManager* BrowserView::CreateLayoutManager() const {
297  return new BrowserViewLayout();
298}
299
300void BrowserView::ChildPreferredSizeChanged(View* child) {
301  Layout();
302}
303
304bool BrowserView::GetSavedWindowBounds(gfx::Rect* bounds) const {
305  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeosFrame)) {
306    // Typically we don't request a full screen size. This means we'll request a
307    // non-full screen size, layout/paint at that size, then the window manager
308    // will snap us to full screen size. This results in an ugly
309    // resize/paint. To avoid this we always request a full screen size.
310    *bounds = views::Screen::GetMonitorWorkAreaNearestWindow(
311        GTK_WIDGET(GetWindow()->GetNativeWindow()));
312    return true;
313  }
314  return ::BrowserView::GetSavedWindowBounds(bounds);
315}
316
317void BrowserView::Cut() {
318  gtk_util::DoCut(this);
319}
320
321void BrowserView::Copy() {
322  gtk_util::DoCopy(this);
323}
324
325void BrowserView::Paste() {
326  gtk_util::DoPaste(this);
327}
328
329// views::ContextMenuController overrides.
330void BrowserView::ShowContextMenuForView(views::View* source,
331                                         const gfx::Point& p,
332                                         bool is_mouse_gesture) {
333  // Only show context menu if point is in unobscured parts of browser, i.e.
334  // if NonClientHitTest returns :
335  // - HTCAPTION: in title bar or unobscured part of tabstrip
336  // - HTNOWHERE: as the name implies.
337  gfx::Point point_in_parent_coords(p);
338  views::View::ConvertPointToView(NULL, parent(), &point_in_parent_coords);
339  int hit_test = NonClientHitTest(point_in_parent_coords);
340  if (hit_test == HTCAPTION || hit_test == HTNOWHERE)
341    system_menu_menu_->RunMenuAt(p, views::Menu2::ALIGN_TOPLEFT);
342}
343
344void BrowserView::OnMenuOpened() {
345  // Save the focused widget before wrench menu opens.
346  saved_focused_widget_ = gtk_window_get_focus(GetNativeHandle());
347}
348
349// StatusAreaHost overrides.
350Profile* BrowserView::GetProfile() const {
351  return browser()->profile();
352}
353
354gfx::NativeWindow BrowserView::GetNativeWindow() const {
355  return GetWindow()->GetNativeWindow();
356}
357
358bool BrowserView::ShouldOpenButtonOptions(
359    const views::View* button_view) const {
360  return true;
361}
362
363void BrowserView::ExecuteBrowserCommand(int id) const {
364  browser()->ExecuteCommand(id);
365}
366
367void BrowserView::OpenButtonOptions(const views::View* button_view) {
368  if (button_view == status_area_->network_view()) {
369    browser()->OpenInternetOptionsDialog();
370  } else if (button_view == status_area_->input_method_view()) {
371    browser()->OpenLanguageOptionsDialog();
372  } else {
373    browser()->OpenSystemOptionsDialog();
374  }
375}
376
377StatusAreaHost::ScreenMode BrowserView::GetScreenMode() const {
378  return kBrowserMode;
379}
380
381StatusAreaHost::TextStyle BrowserView::GetTextStyle() const {
382  ui::ThemeProvider* tp = GetThemeProvider();
383  return tp->HasCustomImage(IDR_THEME_FRAME) ?
384      StatusAreaHost::kWhiteHaloed : (IsOffTheRecord() ?
385          StatusAreaHost::kWhitePlain : StatusAreaHost::kGrayEmbossed);
386}
387
388////////////////////////////////////////////////////////////////////////////////
389// BrowserView protected:
390
391void BrowserView::GetAccessiblePanes(
392    std::vector<AccessiblePaneView*>* panes) {
393  ::BrowserView::GetAccessiblePanes(panes);
394  panes->push_back(status_area_);
395}
396
397////////////////////////////////////////////////////////////////////////////////
398// BrowserView private:
399
400void BrowserView::InitSystemMenu() {
401  system_menu_contents_.reset(new ui::SimpleMenuModel(this));
402  system_menu_contents_->AddItemWithStringId(IDC_RESTORE_TAB,
403                                               IDS_RESTORE_TAB);
404  system_menu_contents_->AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
405  system_menu_contents_->AddSeparator();
406  system_menu_contents_->AddItemWithStringId(IDC_TASK_MANAGER,
407                                               IDS_TASK_MANAGER);
408  system_menu_menu_.reset(new views::Menu2(system_menu_contents_.get()));
409}
410
411}  // namespace chromeos
412
413// static
414BrowserWindow* BrowserWindow::CreateBrowserWindow(Browser* browser) {
415  // Create a browser view for chromeos.
416  BrowserView* view;
417  if (browser->type() & Browser::TYPE_POPUP)
418    view = new chromeos::PanelBrowserView(browser);
419  else
420    view = new chromeos::BrowserView(browser);
421  BrowserFrame::Create(view, browser->profile());
422  return view;
423}
424