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 "base/strings/utf_string_conversions.h" 6 #include "ui/base/hit_test.h" 7 #include "ui/views/bubble/bubble_border.h" 8 #include "ui/views/bubble/bubble_frame_view.h" 9 #include "ui/views/controls/button/checkbox.h" 10 #include "ui/views/controls/button/label_button.h" 11 #include "ui/views/test/views_test_base.h" 12 #include "ui/views/widget/widget.h" 13 #include "ui/views/window/dialog_client_view.h" 14 #include "ui/views/window/dialog_delegate.h" 15 16 namespace views { 17 18 namespace { 19 20 class TestDialog : public DialogDelegateView, public ButtonListener { 21 public: 22 TestDialog() 23 : canceled_(false), 24 accepted_(false), 25 closeable_(false), 26 last_pressed_button_(NULL) {} 27 virtual ~TestDialog() {} 28 29 // DialogDelegateView overrides: 30 virtual bool Cancel() OVERRIDE { 31 canceled_ = true; 32 return closeable_; 33 } 34 virtual bool Accept() OVERRIDE { 35 accepted_ = true; 36 return closeable_; 37 } 38 39 // DialogDelegateView overrides: 40 virtual gfx::Size GetPreferredSize() const OVERRIDE { 41 return gfx::Size(200, 200); 42 } 43 virtual base::string16 GetWindowTitle() const OVERRIDE { return title_; } 44 virtual bool UseNewStyleForThisDialog() const OVERRIDE { return true; } 45 46 // ButtonListener override: 47 virtual void ButtonPressed(Button* sender, const ui::Event& event) OVERRIDE { 48 last_pressed_button_ = sender; 49 } 50 51 Button* last_pressed_button() const { return last_pressed_button_; } 52 53 void PressEnterAndCheckStates(Button* button) { 54 ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, 0, false); 55 GetFocusManager()->OnKeyEvent(key_event); 56 const DialogClientView* client_view = GetDialogClientView(); 57 EXPECT_EQ(canceled_, client_view->cancel_button()->is_default()); 58 EXPECT_EQ(accepted_, client_view->ok_button()->is_default()); 59 // This view does not listen for ok or cancel clicks, DialogClientView does. 60 CheckAndResetStates(button == client_view->cancel_button(), 61 button == client_view->ok_button(), 62 (canceled_ || accepted_ ) ? NULL : button); 63 } 64 65 void CheckAndResetStates(bool canceled, bool accepted, Button* last_pressed) { 66 EXPECT_EQ(canceled, canceled_); 67 canceled_ = false; 68 EXPECT_EQ(accepted, accepted_); 69 accepted_ = false; 70 EXPECT_EQ(last_pressed, last_pressed_button_); 71 last_pressed_button_ = NULL; 72 } 73 74 void TearDown() { 75 closeable_ = true; 76 GetWidget()->Close(); 77 } 78 79 void set_title(const base::string16& title) { title_ = title; } 80 81 private: 82 bool canceled_; 83 bool accepted_; 84 // Prevent the dialog from closing, for repeated ok and cancel button clicks. 85 bool closeable_; 86 Button* last_pressed_button_; 87 base::string16 title_; 88 89 DISALLOW_COPY_AND_ASSIGN(TestDialog); 90 }; 91 92 class DialogTest : public ViewsTestBase { 93 public: 94 DialogTest() : dialog_(NULL) {} 95 virtual ~DialogTest() {} 96 97 virtual void SetUp() OVERRIDE { 98 ViewsTestBase::SetUp(); 99 dialog_ = new TestDialog(); 100 DialogDelegate::CreateDialogWidget(dialog_, GetContext(), NULL)->Show(); 101 } 102 103 virtual void TearDown() OVERRIDE { 104 dialog_->TearDown(); 105 ViewsTestBase::TearDown(); 106 } 107 108 TestDialog* dialog() const { return dialog_; } 109 110 private: 111 TestDialog* dialog_; 112 113 DISALLOW_COPY_AND_ASSIGN(DialogTest); 114 }; 115 116 } // namespace 117 118 TEST_F(DialogTest, DefaultButtons) { 119 DialogClientView* client_view = dialog()->GetDialogClientView(); 120 LabelButton* ok_button = client_view->ok_button(); 121 122 // DialogDelegate's default button (ok) should be default (and handle enter). 123 EXPECT_EQ(ui::DIALOG_BUTTON_OK, dialog()->GetDefaultDialogButton()); 124 dialog()->PressEnterAndCheckStates(ok_button); 125 126 // Focus another button in the dialog, it should become the default. 127 LabelButton* button_1 = new LabelButton(dialog(), base::string16()); 128 client_view->AddChildView(button_1); 129 client_view->OnWillChangeFocus(ok_button, button_1); 130 EXPECT_TRUE(button_1->is_default()); 131 dialog()->PressEnterAndCheckStates(button_1); 132 133 // Focus a Checkbox (not a push button), OK should become the default again. 134 Checkbox* checkbox = new Checkbox(base::string16()); 135 client_view->AddChildView(checkbox); 136 client_view->OnWillChangeFocus(button_1, checkbox); 137 EXPECT_FALSE(button_1->is_default()); 138 dialog()->PressEnterAndCheckStates(ok_button); 139 140 // Focus yet another button in the dialog, it should become the default. 141 LabelButton* button_2 = new LabelButton(dialog(), base::string16()); 142 client_view->AddChildView(button_2); 143 client_view->OnWillChangeFocus(checkbox, button_2); 144 EXPECT_FALSE(button_1->is_default()); 145 EXPECT_TRUE(button_2->is_default()); 146 dialog()->PressEnterAndCheckStates(button_2); 147 148 // Focus nothing, OK should become the default again. 149 client_view->OnWillChangeFocus(button_2, NULL); 150 EXPECT_FALSE(button_1->is_default()); 151 EXPECT_FALSE(button_2->is_default()); 152 dialog()->PressEnterAndCheckStates(ok_button); 153 } 154 155 TEST_F(DialogTest, AcceptAndCancel) { 156 DialogClientView* client_view = dialog()->GetDialogClientView(); 157 LabelButton* ok_button = client_view->ok_button(); 158 LabelButton* cancel_button = client_view->cancel_button(); 159 160 // Check that return/escape accelerators accept/cancel dialogs. 161 const ui::KeyEvent return_key(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, 0, false); 162 dialog()->GetFocusManager()->OnKeyEvent(return_key); 163 dialog()->CheckAndResetStates(false, true, NULL); 164 const ui::KeyEvent escape_key(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0, false); 165 dialog()->GetFocusManager()->OnKeyEvent(escape_key); 166 dialog()->CheckAndResetStates(true, false, NULL); 167 168 // Check ok and cancel button behavior on a directed return key events. 169 ok_button->OnKeyPressed(return_key); 170 dialog()->CheckAndResetStates(false, true, NULL); 171 cancel_button->OnKeyPressed(return_key); 172 dialog()->CheckAndResetStates(true, false, NULL); 173 174 // Check that return accelerators cancel dialogs if cancel is focused. 175 cancel_button->RequestFocus(); 176 dialog()->GetFocusManager()->OnKeyEvent(return_key); 177 dialog()->CheckAndResetStates(true, false, NULL); 178 } 179 180 TEST_F(DialogTest, RemoveDefaultButton) { 181 // Removing buttons from the dialog here should not cause a crash on close. 182 delete dialog()->GetDialogClientView()->ok_button(); 183 delete dialog()->GetDialogClientView()->cancel_button(); 184 } 185 186 TEST_F(DialogTest, HitTest) { 187 // Ensure that the new style's BubbleFrameView hit-tests as expected. 188 const NonClientView* view = dialog()->GetWidget()->non_client_view(); 189 BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view()); 190 const int border = frame->bubble_border()->GetBorderThickness(); 191 192 struct { 193 const int point; 194 const int hit; 195 } cases[] = { 196 { border, HTSYSMENU }, 197 { border + 10, HTSYSMENU }, 198 { border + 20, HTCAPTION }, 199 { border + 40, HTCLIENT }, 200 { border + 50, HTCLIENT }, 201 { 1000, HTNOWHERE }, 202 }; 203 204 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { 205 gfx::Point point(cases[i].point, cases[i].point); 206 EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) 207 << " with border: " << border << ", at point " << cases[i].point; 208 } 209 } 210 211 TEST_F(DialogTest, BoundsAccommodateTitle) { 212 TestDialog* dialog2(new TestDialog()); 213 dialog2->set_title(base::ASCIIToUTF16("Title")); 214 DialogDelegate::CreateDialogWidget(dialog2, GetContext(), NULL); 215 216 // Titled dialogs have taller initial frame bounds than untitled dialogs. 217 View* frame1 = dialog()->GetWidget()->non_client_view()->frame_view(); 218 View* frame2 = dialog2->GetWidget()->non_client_view()->frame_view(); 219 EXPECT_LT(frame1->GetPreferredSize().height(), 220 frame2->GetPreferredSize().height()); 221 222 // Giving the default test dialog a title will yield the same bounds. 223 dialog()->set_title(base::ASCIIToUTF16("Title")); 224 dialog()->GetWidget()->UpdateWindowTitle(); 225 EXPECT_EQ(frame1->GetPreferredSize().height(), 226 frame2->GetPreferredSize().height()); 227 } 228 229 } // namespace views 230