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
43 namespace {
44
45 // Amount to offset the toolbar by when vertical tabs are enabled.
46 const int kVerticalTabStripToolbarOffset = 2;
47 // Amount to tweak the position of the status area to get it to look right.
48 const int kStatusAreaVerticalAdjustment = -1;
49
50 // If a popup window is larger than this fraction of the screen, create a tab.
51 const float kPopupMaxWidthFactor = 0.5;
52 const float kPopupMaxHeightFactor = 0.6;
53
54 } // namespace
55
56 namespace chromeos {
57
58 // LayoutManager for BrowserView, which layouts extra components such as
59 // the status views as follows:
60 // ____ __ __
61 // / \ \ \ [StatusArea]
62 //
63 class BrowserViewLayout : public ::BrowserViewLayout {
64 public:
BrowserViewLayout()65 BrowserViewLayout() : ::BrowserViewLayout() {}
~BrowserViewLayout()66 virtual ~BrowserViewLayout() {}
67
68 //////////////////////////////////////////////////////////////////////////////
69 // BrowserViewLayout overrides:
70
Installed(views::View * host)71 void Installed(views::View* host) {
72 status_area_ = NULL;
73 ::BrowserViewLayout::Installed(host);
74 }
75
ViewAdded(views::View * host,views::View * view)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
LayoutTabStrip()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
LayoutToolbar(int top)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
IsPositionInWindowCaption(const gfx::Point & point)118 virtual bool IsPositionInWindowCaption(const gfx::Point& point) {
119 return ::BrowserViewLayout::IsPositionInWindowCaption(point)
120 && !IsPointInViewsInTitleArea(point);
121 }
122
NonClientHitTest(const gfx::Point & point)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:
chromeos_browser_view()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.
IsPointInViewsInTitleArea(const gfx::Point & point) const139 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.
LayoutTitlebarComponentsWithVerticalTabs(const gfx::Rect & bounds)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|).
LayoutTitlebarComponents(const gfx::Rect & 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
BrowserView(Browser * browser)221 BrowserView::BrowserView(Browser* browser)
222 : ::BrowserView(browser),
223 status_area_(NULL),
224 saved_focused_widget_(NULL) {
225 }
226
~BrowserView()227 BrowserView::~BrowserView() {
228 if (toolbar())
229 toolbar()->RemoveMenuListener(this);
230 }
231
232 ////////////////////////////////////////////////////////////////////////////////
233 // BrowserView, ::BrowserView overrides:
234
Init()235 void 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 ¶ms);
263 }
264
Show()265 void BrowserView::Show() {
266 ShowInternal(true);
267 }
268
ShowInactive()269 void BrowserView::ShowInactive() {
270 ShowInternal(false);
271 }
272
ShowInternal(bool is_active)273 void 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 ¶ms);
288 }
289 }
290
FocusChromeOSStatus()291 void BrowserView::FocusChromeOSStatus() {
292 SaveFocusedView();
293 status_area_->SetPaneFocus(last_focused_view_storage_id(), NULL);
294 }
295
CreateLayoutManager() const296 views::LayoutManager* BrowserView::CreateLayoutManager() const {
297 return new BrowserViewLayout();
298 }
299
ChildPreferredSizeChanged(View * child)300 void BrowserView::ChildPreferredSizeChanged(View* child) {
301 Layout();
302 }
303
GetSavedWindowBounds(gfx::Rect * bounds) const304 bool 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
Cut()317 void BrowserView::Cut() {
318 gtk_util::DoCut(this);
319 }
320
Copy()321 void BrowserView::Copy() {
322 gtk_util::DoCopy(this);
323 }
324
Paste()325 void BrowserView::Paste() {
326 gtk_util::DoPaste(this);
327 }
328
329 // views::ContextMenuController overrides.
ShowContextMenuForView(views::View * source,const gfx::Point & p,bool is_mouse_gesture)330 void 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
OnMenuOpened()344 void 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.
GetProfile() const350 Profile* BrowserView::GetProfile() const {
351 return browser()->profile();
352 }
353
GetNativeWindow() const354 gfx::NativeWindow BrowserView::GetNativeWindow() const {
355 return GetWindow()->GetNativeWindow();
356 }
357
ShouldOpenButtonOptions(const views::View * button_view) const358 bool BrowserView::ShouldOpenButtonOptions(
359 const views::View* button_view) const {
360 return true;
361 }
362
ExecuteBrowserCommand(int id) const363 void BrowserView::ExecuteBrowserCommand(int id) const {
364 browser()->ExecuteCommand(id);
365 }
366
OpenButtonOptions(const views::View * button_view)367 void 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
GetScreenMode() const377 StatusAreaHost::ScreenMode BrowserView::GetScreenMode() const {
378 return kBrowserMode;
379 }
380
GetTextStyle() const381 StatusAreaHost::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
GetAccessiblePanes(std::vector<AccessiblePaneView * > * panes)391 void BrowserView::GetAccessiblePanes(
392 std::vector<AccessiblePaneView*>* panes) {
393 ::BrowserView::GetAccessiblePanes(panes);
394 panes->push_back(status_area_);
395 }
396
397 ////////////////////////////////////////////////////////////////////////////////
398 // BrowserView private:
399
InitSystemMenu()400 void 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
CreateBrowserWindow(Browser * browser)414 BrowserWindow* 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