1 // Copyright 2013 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/ui/views/frame/opaque_browser_frame_view_layout.h"
6
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/views/avatar_menu_button.h"
11 #include "chrome/browser/ui/views/tab_icon_view.h"
12 #include "chrome/browser/ui/views/tabs/tab.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "ui/gfx/image/image_skia.h"
15 #include "ui/gfx/image/image_skia_rep.h"
16 #include "ui/gfx/text_constants.h"
17 #include "ui/views/controls/button/image_button.h"
18 #include "ui/views/controls/button/menu_button.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/test/views_test_base.h"
21
22 using views::Widget;
23
24 namespace {
25
26 const int kWidth = 500;
27
28 class TestLayoutDelegate : public OpaqueBrowserFrameViewLayoutDelegate {
29 public:
30 enum WindowState {
31 STATE_NORMAL,
32 STATE_MAXIMIZED,
33 STATE_MINIMIZED,
34 STATE_FULLSCREEN
35 };
36
TestLayoutDelegate()37 TestLayoutDelegate()
38 : show_avatar_(false),
39 show_caption_buttons_(true),
40 window_state_(STATE_NORMAL) {
41 }
42
~TestLayoutDelegate()43 virtual ~TestLayoutDelegate() {}
44
SetWindowTitle(const base::string16 & title)45 void SetWindowTitle(const base::string16& title) {
46 window_title_ = title;
47 }
48
SetShouldShowAvatar(bool show_avatar)49 void SetShouldShowAvatar(bool show_avatar) {
50 show_avatar_ = show_avatar;
51 }
52
SetShouldShowCaptionButtons(bool show_caption_buttons)53 void SetShouldShowCaptionButtons(bool show_caption_buttons) {
54 show_caption_buttons_ = show_caption_buttons;
55 }
56
SetWindowState(WindowState state)57 void SetWindowState(WindowState state) {
58 window_state_ = state;
59 }
60
61 // OpaqueBrowserFrameViewLayoutDelegate overrides:
62
ShouldShowWindowIcon() const63 virtual bool ShouldShowWindowIcon() const OVERRIDE {
64 return !window_title_.empty();
65 }
66
ShouldShowWindowTitle() const67 virtual bool ShouldShowWindowTitle() const OVERRIDE {
68 return !window_title_.empty();
69 }
70
GetWindowTitle() const71 virtual base::string16 GetWindowTitle() const OVERRIDE {
72 return window_title_;
73 }
74
GetIconSize() const75 virtual int GetIconSize() const OVERRIDE {
76 // The value on linux_aura and non-aura windows.
77 return 17;
78 }
79
ShouldLeaveOffsetNearTopBorder() const80 virtual bool ShouldLeaveOffsetNearTopBorder() const OVERRIDE {
81 return !IsMaximized();
82 }
83
GetBrowserViewMinimumSize() const84 virtual gfx::Size GetBrowserViewMinimumSize() const OVERRIDE {
85 // Taken from a calculation in BrowserViewLayout.
86 return gfx::Size(168, 64);
87 }
88
ShouldShowCaptionButtons() const89 virtual bool ShouldShowCaptionButtons() const OVERRIDE {
90 return show_caption_buttons_;
91 }
92
ShouldShowAvatar() const93 virtual bool ShouldShowAvatar() const OVERRIDE {
94 return show_avatar_;
95 }
96
IsRegularOrGuestSession() const97 virtual bool IsRegularOrGuestSession() const OVERRIDE {
98 return true;
99 }
100
GetOTRAvatarIcon() const101 virtual gfx::ImageSkia GetOTRAvatarIcon() const OVERRIDE {
102 // The calculations depend on the size of the OTR resource, and chromeos
103 // uses a different sized image, so hard code the size of the current
104 // windows/linux one.
105 gfx::ImageSkiaRep rep(gfx::Size(40, 29), 1.0f);
106 gfx::ImageSkia image(rep);
107 return image;
108 }
109
IsMaximized() const110 virtual bool IsMaximized() const OVERRIDE {
111 return window_state_ == STATE_MAXIMIZED;
112 }
113
IsMinimized() const114 virtual bool IsMinimized() const OVERRIDE {
115 return window_state_ == STATE_MINIMIZED;
116 }
117
IsFullscreen() const118 virtual bool IsFullscreen() const OVERRIDE {
119 return window_state_ == STATE_FULLSCREEN;
120 }
121
IsTabStripVisible() const122 virtual bool IsTabStripVisible() const OVERRIDE {
123 return window_title_.empty();
124 }
125
GetTabStripHeight() const126 virtual int GetTabStripHeight() const OVERRIDE {
127 return IsTabStripVisible() ? Tab::GetMinimumUnselectedSize().height() : 0;
128 }
129
GetAdditionalReservedSpaceInTabStrip() const130 virtual int GetAdditionalReservedSpaceInTabStrip() const OVERRIDE {
131 return 0;
132 }
133
GetTabstripPreferredSize() const134 virtual gfx::Size GetTabstripPreferredSize() const OVERRIDE {
135 // Measured from Tabstrip::GetPreferredSize().
136 return IsTabStripVisible() ? gfx::Size(78, 29) : gfx::Size(0, 0);
137 }
138
139 private:
140 base::string16 window_title_;
141 bool show_avatar_;
142 bool show_caption_buttons_;
143 WindowState window_state_;
144
145 DISALLOW_COPY_AND_ASSIGN(TestLayoutDelegate);
146 };
147
148 } // namespace
149
150 class OpaqueBrowserFrameViewLayoutTest : public views::ViewsTestBase {
151 public:
OpaqueBrowserFrameViewLayoutTest()152 OpaqueBrowserFrameViewLayoutTest() {}
~OpaqueBrowserFrameViewLayoutTest()153 virtual ~OpaqueBrowserFrameViewLayoutTest() {}
154
SetUp()155 virtual void SetUp() OVERRIDE {
156 views::ViewsTestBase::SetUp();
157
158 delegate_.reset(new TestLayoutDelegate);
159 layout_manager_ = new OpaqueBrowserFrameViewLayout(delegate_.get());
160 layout_manager_->set_extra_caption_y(0);
161 layout_manager_->set_window_caption_spacing(0);
162 widget_ = new Widget;
163 widget_->Init(CreateParams(Widget::InitParams::TYPE_POPUP));
164 root_view_ = widget_->GetRootView();
165 root_view_->SetSize(gfx::Size(kWidth, kWidth));
166 root_view_->SetLayoutManager(layout_manager_);
167
168 // Add the caption buttons. We use fake images because we're modeling the
169 // Windows assets here, while the linux version uses differently sized
170 // assets.
171 //
172 // TODO(erg): In a follow up patch, separate these sizes out into virtual
173 // accessors so we can test both the windows and linux behaviours once we
174 // start modifying the code.
175 minimize_button_ = InitWindowCaptionButton(
176 VIEW_ID_MINIMIZE_BUTTON, gfx::Size(26, 18));
177 maximize_button_ = InitWindowCaptionButton(
178 VIEW_ID_MAXIMIZE_BUTTON, gfx::Size(25, 18));
179 restore_button_ = InitWindowCaptionButton(
180 VIEW_ID_RESTORE_BUTTON, gfx::Size(25, 18));
181 close_button_ = InitWindowCaptionButton(
182 VIEW_ID_CLOSE_BUTTON, gfx::Size(43, 18));
183 }
184
TearDown()185 virtual void TearDown() OVERRIDE {
186 widget_->CloseNow();
187
188 views::ViewsTestBase::TearDown();
189 }
190
191 protected:
InitWindowCaptionButton(ViewID view_id,const gfx::Size & size)192 views::ImageButton* InitWindowCaptionButton(ViewID view_id,
193 const gfx::Size& size) {
194 views::ImageButton* button = new views::ImageButton(NULL);
195 gfx::ImageSkiaRep rep(size, 1.0f);
196 gfx::ImageSkia image(rep);
197 button->SetImage(views::CustomButton::STATE_NORMAL, &image);
198 button->set_id(view_id);
199 root_view_->AddChildView(button);
200 return button;
201 }
202
AddWindowTitleIcons()203 void AddWindowTitleIcons() {
204 tab_icon_view_ = new TabIconView(NULL);
205 tab_icon_view_->set_is_light(true);
206 tab_icon_view_->set_id(VIEW_ID_WINDOW_ICON);
207 root_view_->AddChildView(tab_icon_view_);
208
209 window_title_ = new views::Label(delegate_->GetWindowTitle(),
210 default_font_);
211 window_title_->SetVisible(delegate_->ShouldShowWindowTitle());
212 window_title_->SetEnabledColor(SK_ColorWHITE);
213 window_title_->SetBackgroundColor(0x00000000);
214 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
215 window_title_->set_id(VIEW_ID_WINDOW_TITLE);
216 root_view_->AddChildView(window_title_);
217 }
218
AddAvatarButton()219 void AddAvatarButton() {
220 menu_button_ = new AvatarMenuButton(NULL, false);
221 menu_button_->set_id(VIEW_ID_AVATAR_BUTTON);
222 delegate_->SetShouldShowAvatar(true);
223 root_view_->AddChildView(menu_button_);
224 }
225
AddAvatarLabel()226 void AddAvatarLabel() {
227 avatar_label_ = new views::MenuButton(NULL, base::string16(), NULL, false);
228 avatar_label_->set_id(VIEW_ID_AVATAR_LABEL);
229 root_view_->AddChildView(avatar_label_);
230
231 // The avatar label should only be used together with the avatar button.
232 AddAvatarButton();
233 }
234
AddNewAvatarButton()235 void AddNewAvatarButton() {
236 new_avatar_button_ =
237 new views::MenuButton(NULL, base::string16(), NULL, false);
238 new_avatar_button_->set_id(VIEW_ID_NEW_AVATAR_BUTTON);
239 root_view_->AddChildView(new_avatar_button_);
240 }
241
ExpectBasicWindowBounds()242 void ExpectBasicWindowBounds() {
243 EXPECT_EQ("428,1 25x18", maximize_button_->bounds().ToString());
244 EXPECT_EQ("402,1 26x18", minimize_button_->bounds().ToString());
245 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
246 EXPECT_EQ("453,1 43x18", close_button_->bounds().ToString());
247 }
248
249 gfx::Font default_font_;
250
251 Widget* widget_;
252 views::View* root_view_;
253 OpaqueBrowserFrameViewLayout* layout_manager_;
254 scoped_ptr<TestLayoutDelegate> delegate_;
255
256 // Widgets:
257 views::ImageButton* minimize_button_;
258 views::ImageButton* maximize_button_;
259 views::ImageButton* restore_button_;
260 views::ImageButton* close_button_;
261
262 TabIconView* tab_icon_view_;
263 views::Label* window_title_;
264
265 AvatarMenuButton* menu_button_;
266 views::MenuButton* avatar_label_;
267 views::MenuButton* new_avatar_button_;
268
269 DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayoutTest);
270 };
271
TEST_F(OpaqueBrowserFrameViewLayoutTest,BasicWindow)272 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindow) {
273 // Tests the layout of a default chrome window with no avatars, no window
274 // titles, and a tabstrip.
275 root_view_->Layout();
276
277 ExpectBasicWindowBounds();
278
279 // After some visual inspection, it really does look like the tabstrip is
280 // initally positioned out of our view.
281 EXPECT_EQ("-1,13 398x29",
282 layout_manager_->GetBoundsForTabStrip(
283 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
284 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
285
286 // A normal window with no window icon still produces icon bounds for
287 // Windows, which has a hidden icon that a user can double click on to close
288 // the window.
289 EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
290 }
291
TEST_F(OpaqueBrowserFrameViewLayoutTest,BasicWindowMaximized)292 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindowMaximized) {
293 // Tests the layout of a default chrome window with no avatars, no window
294 // titles, and a tabstrip, but maximized this time.
295 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
296 root_view_->Layout();
297
298 // Note how the bonds start at the exact top of the window while maximized
299 // while they start 1 pixel below when unmaximized.
300 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
301 EXPECT_EQ("403,0 26x18", minimize_button_->bounds().ToString());
302 EXPECT_EQ("429,0 25x18", restore_button_->bounds().ToString());
303 EXPECT_EQ("454,0 46x18", close_button_->bounds().ToString());
304
305 EXPECT_EQ("-5,-3 392x29",
306 layout_manager_->GetBoundsForTabStrip(
307 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
308 EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
309
310 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
311 // this rect, extended to the top left corner of the window.
312 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
313 }
314
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowButtonsOnLeft)315 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowButtonsOnLeft) {
316 // Tests the layout of a chrome window with caption buttons on the left.
317 std::vector<views::FrameButton> leading_buttons;
318 std::vector<views::FrameButton> trailing_buttons;
319 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
320 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
321 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
322 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
323 root_view_->Layout();
324
325 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
326 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
327 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
328 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
329
330 EXPECT_EQ("93,13 398x29",
331 layout_manager_->GetBoundsForTabStrip(
332 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
333 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
334
335 // If the buttons are on the left, there should be no hidden icon for the user
336 // to double click.
337 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
338 }
339
TEST_F(OpaqueBrowserFrameViewLayoutTest,WithoutCaptionButtons)340 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithoutCaptionButtons) {
341 // Tests the layout of a default chrome window with no caption buttons.
342 delegate_->SetShouldShowCaptionButtons(false);
343 root_view_->Layout();
344
345 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
346 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
347 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
348 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
349
350 EXPECT_EQ("-1,13 492x29",
351 layout_manager_->GetBoundsForTabStrip(
352 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
353 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
354
355 // A normal window with no window icon still produces icon bounds for
356 // Windows, which has a hidden icon that a user can double click on to close
357 // the window.
358 EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
359 }
360
TEST_F(OpaqueBrowserFrameViewLayoutTest,WithWindowTitleAndIcon)361 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithWindowTitleAndIcon) {
362 // Tests the layout of pop up windows.
363 delegate_->SetWindowTitle(ASCIIToUTF16("Window Title"));
364 AddWindowTitleIcons();
365 root_view_->Layout();
366
367 // We should have the right hand side should match the BasicWindow case.
368 ExpectBasicWindowBounds();
369
370 // Check the location of the tab icon and window title.
371 EXPECT_EQ("6,3 17x17", tab_icon_view_->bounds().ToString());
372 EXPECT_EQ("27,3 370x17", window_title_->bounds().ToString());
373 }
374
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowWithAvatar)375 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatar) {
376 // Tests a normal tabstrip window with an avatar icon.
377 AddAvatarButton();
378 root_view_->Layout();
379
380 ExpectBasicWindowBounds();
381
382 // Check the location of the avatar
383 EXPECT_EQ("7,11 40x29", menu_button_->bounds().ToString());
384 EXPECT_EQ("45,13 352x29",
385 layout_manager_->GetBoundsForTabStrip(
386 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
387 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
388 }
389
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowWithAvatarWithButtonsOnLeft)390 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarWithButtonsOnLeft) {
391 // Tests the layout of a chrome window with an avatar icon and caption buttons
392 // on the left. The avatar icon should therefore be on the right.
393 AddAvatarButton();
394 std::vector<views::FrameButton> leading_buttons;
395 std::vector<views::FrameButton> trailing_buttons;
396 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
397 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
398 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
399 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
400 root_view_->Layout();
401
402 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
403 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
404 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
405 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
406
407 // Check the location of the avatar
408 EXPECT_EQ("454,11 40x29", menu_button_->bounds().ToString());
409 EXPECT_EQ("93,13 356x29",
410 layout_manager_->GetBoundsForTabStrip(
411 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
412 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
413
414 // This means that the menu will pop out facing the left (if it were to face
415 // the right, it would go outside the window frame and be clipped).
416 EXPECT_TRUE(menu_button_->button_on_right());
417
418 // If the buttons are on the left, there should be no hidden icon for the user
419 // to double click.
420 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
421 }
422
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowWithAvatarWithoutCaptionButtonsOnLeft)423 TEST_F(OpaqueBrowserFrameViewLayoutTest,
424 WindowWithAvatarWithoutCaptionButtonsOnLeft) {
425 // Tests the layout of a chrome window with an avatar icon and no caption
426 // buttons. However, the caption buttons *would* be on the left if they
427 // weren't hidden, and therefore, the avatar icon should be on the right.
428 AddAvatarButton();
429 std::vector<views::FrameButton> leading_buttons;
430 std::vector<views::FrameButton> trailing_buttons;
431 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
432 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
433 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
434 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
435 delegate_->SetShouldShowCaptionButtons(false);
436 root_view_->Layout();
437
438 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
439 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
440 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
441 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
442
443 // Check the location of the avatar
444 EXPECT_EQ("454,11 40x29", menu_button_->bounds().ToString());
445 EXPECT_EQ("-1,13 450x29",
446 layout_manager_->GetBoundsForTabStrip(
447 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
448 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
449
450 // A normal window with no window icon still produces icon bounds for
451 // Windows, which has a hidden icon that a user can double click on to close
452 // the window.
453 EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
454 }
455
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowWithNewAvatar)456 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithNewAvatar) {
457 CommandLine::ForCurrentProcess()->AppendSwitch(
458 switches::kNewProfileManagement);
459
460 // Tests a normal tabstrip window with the new style avatar icon.
461 AddNewAvatarButton();
462 root_view_->Layout();
463
464 ExpectBasicWindowBounds();
465
466 // Check the location of the caption button
467 EXPECT_EQ("385,1 12x20", new_avatar_button_->bounds().ToString());
468 // The basic window bounds are (-1, 13 398x29). There should not be an icon
469 // avatar in the left, and the new avatar button has an offset of 5 to its
470 // next control.
471 EXPECT_EQ("-1,13 381x29",
472 layout_manager_->GetBoundsForTabStrip(
473 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
474 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
475 }
476
TEST_F(OpaqueBrowserFrameViewLayoutTest,WindowWithAvatarLabelAndButton)477 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarLabelAndButton) {
478 AddAvatarLabel();
479 root_view_->Layout();
480
481 ExpectBasicWindowBounds();
482
483 // Check the location of the avatar label relative to the avatar button.
484 // The label height and width depends on the font size and the text displayed.
485 // This may possibly change, so we don't test it here.
486 EXPECT_EQ(menu_button_->bounds().x() - 2, avatar_label_->bounds().x());
487 EXPECT_EQ(
488 menu_button_->bounds().bottom() - 3 - avatar_label_->bounds().height(),
489 avatar_label_->bounds().y());
490 }
491