1 // Copyright (c) 2012 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 "ui/views/window/dialog_client_view.h" 6 7 #include <algorithm> 8 9 #include "ui/events/keycodes/keyboard_codes.h" 10 #include "ui/views/background.h" 11 #include "ui/views/controls/button/blue_button.h" 12 #include "ui/views/controls/button/label_button.h" 13 #include "ui/views/layout/layout_constants.h" 14 #include "ui/views/widget/widget.h" 15 #include "ui/views/window/dialog_delegate.h" 16 17 namespace views { 18 19 namespace { 20 21 // The group used by the buttons. This name is chosen voluntarily big not to 22 // conflict with other groups that could be in the dialog content. 23 const int kButtonGroup = 6666; 24 25 // Returns true if the given view should be shown (i.e. exists and is 26 // visible). 27 bool ShouldShow(View* view) { 28 return view && view->visible(); 29 } 30 31 } // namespace 32 33 /////////////////////////////////////////////////////////////////////////////// 34 // DialogClientView, public: 35 36 DialogClientView::DialogClientView(Widget* owner, View* contents_view) 37 : ClientView(owner, contents_view), 38 ok_button_(NULL), 39 cancel_button_(NULL), 40 default_button_(NULL), 41 focus_manager_(NULL), 42 extra_view_(NULL), 43 footnote_view_(NULL), 44 notified_delegate_(false) { 45 } 46 47 DialogClientView::~DialogClientView() { 48 } 49 50 void DialogClientView::AcceptWindow() { 51 // Only notify the delegate once. See |notified_delegate_|'s comment. 52 if (!notified_delegate_ && GetDialogDelegate()->Accept(false)) { 53 notified_delegate_ = true; 54 Close(); 55 } 56 } 57 58 void DialogClientView::CancelWindow() { 59 // Only notify the delegate once. See |notified_delegate_|'s comment. 60 if (!notified_delegate_ && GetDialogDelegate()->Cancel()) { 61 notified_delegate_ = true; 62 Close(); 63 } 64 } 65 66 void DialogClientView::UpdateDialogButtons() { 67 const int buttons = GetDialogDelegate()->GetDialogButtons(); 68 ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE); 69 if (default_button_) 70 default_button_->SetIsDefault(false); 71 default_button_ = NULL; 72 73 if (buttons & ui::DIALOG_BUTTON_OK) { 74 if (!ok_button_) { 75 ok_button_ = CreateDialogButton(ui::DIALOG_BUTTON_OK); 76 if (!(buttons & ui::DIALOG_BUTTON_CANCEL)) 77 ok_button_->AddAccelerator(escape); 78 AddChildView(ok_button_); 79 } 80 81 UpdateButton(ok_button_, ui::DIALOG_BUTTON_OK); 82 } else if (ok_button_) { 83 delete ok_button_; 84 ok_button_ = NULL; 85 } 86 87 if (buttons & ui::DIALOG_BUTTON_CANCEL) { 88 if (!cancel_button_) { 89 cancel_button_ = CreateDialogButton(ui::DIALOG_BUTTON_CANCEL); 90 cancel_button_->AddAccelerator(escape); 91 AddChildView(cancel_button_); 92 } 93 94 UpdateButton(cancel_button_, ui::DIALOG_BUTTON_CANCEL); 95 } else if (cancel_button_) { 96 delete cancel_button_; 97 cancel_button_ = NULL; 98 } 99 100 // Use the escape key to close the window if there are no dialog buttons. 101 if (!has_dialog_buttons()) 102 AddAccelerator(escape); 103 else 104 ResetAccelerators(); 105 } 106 107 /////////////////////////////////////////////////////////////////////////////// 108 // DialogClientView, ClientView overrides: 109 110 bool DialogClientView::CanClose() { 111 if (notified_delegate_) 112 return true; 113 114 // The dialog is closing but no Accept or Cancel action has been performed 115 // before: it's a Close action. 116 if (GetDialogDelegate()->Close()) { 117 notified_delegate_ = true; 118 GetDialogDelegate()->OnClosed(); 119 return true; 120 } 121 return false; 122 } 123 124 DialogClientView* DialogClientView::AsDialogClientView() { 125 return this; 126 } 127 128 const DialogClientView* DialogClientView::AsDialogClientView() const { 129 return this; 130 } 131 132 void DialogClientView::OnWillChangeFocus(View* focused_before, 133 View* focused_now) { 134 // Make the newly focused button default or restore the dialog's default. 135 const int default_button = GetDialogDelegate()->GetDefaultDialogButton(); 136 LabelButton* new_default_button = NULL; 137 if (focused_now && 138 !strcmp(focused_now->GetClassName(), LabelButton::kViewClassName)) { 139 new_default_button = static_cast<LabelButton*>(focused_now); 140 } else if (default_button == ui::DIALOG_BUTTON_OK && ok_button_) { 141 new_default_button = ok_button_; 142 } else if (default_button == ui::DIALOG_BUTTON_CANCEL && cancel_button_) { 143 new_default_button = cancel_button_; 144 } 145 146 if (default_button_ && default_button_ != new_default_button) 147 default_button_->SetIsDefault(false); 148 default_button_ = new_default_button; 149 if (default_button_ && !default_button_->is_default()) 150 default_button_->SetIsDefault(true); 151 } 152 153 void DialogClientView::OnDidChangeFocus(View* focused_before, 154 View* focused_now) { 155 } 156 157 //////////////////////////////////////////////////////////////////////////////// 158 // DialogClientView, View overrides: 159 160 gfx::Size DialogClientView::GetPreferredSize() { 161 // Initialize the size to fit the buttons and extra view row. 162 gfx::Size size( 163 (ok_button_ ? ok_button_->GetPreferredSize().width() : 0) + 164 (cancel_button_ ? cancel_button_->GetPreferredSize().width() : 0) + 165 (cancel_button_ && ok_button_ ? kRelatedButtonHSpacing : 0) + 166 (ShouldShow(extra_view_) ? extra_view_->GetPreferredSize().width() : 0) + 167 (ShouldShow(extra_view_) && has_dialog_buttons() ? 168 kRelatedButtonHSpacing : 0), 169 0); 170 171 int buttons_height = GetButtonsAndExtraViewRowHeight(); 172 if (buttons_height != 0) { 173 size.Enlarge(0, buttons_height + kRelatedControlVerticalSpacing); 174 // Inset the buttons and extra view. 175 const gfx::Insets insets = GetButtonRowInsets(); 176 size.Enlarge(insets.width(), insets.height()); 177 } 178 179 // Increase the size as needed to fit the contents view. 180 // NOTE: The contents view is not inset on the top or side client view edges. 181 gfx::Size contents_size = contents_view()->GetPreferredSize(); 182 size.Enlarge(0, contents_size.height()); 183 size.set_width(std::max(size.width(), contents_size.width())); 184 185 // Increase the size as needed to fit the footnote view. 186 if (ShouldShow(footnote_view_)) { 187 gfx::Size footnote_size = footnote_view_->GetPreferredSize(); 188 if (!footnote_size.IsEmpty()) 189 size.set_width(std::max(size.width(), footnote_size.width())); 190 191 int footnote_height = footnote_view_->GetHeightForWidth(size.width()); 192 size.Enlarge(0, footnote_height); 193 } 194 195 return size; 196 } 197 198 void DialogClientView::Layout() { 199 gfx::Rect bounds = GetContentsBounds(); 200 201 // Layout the footnote view. 202 if (ShouldShow(footnote_view_)) { 203 const int height = footnote_view_->GetHeightForWidth(bounds.width()); 204 footnote_view_->SetBounds(bounds.x(), bounds.bottom() - height, 205 bounds.width(), height); 206 if (height != 0) 207 bounds.Inset(0, 0, 0, height); 208 } 209 210 // Layout the row containing the buttons and the extra view. 211 if (has_dialog_buttons() || ShouldShow(extra_view_)) { 212 bounds.Inset(GetButtonRowInsets()); 213 const int height = GetButtonsAndExtraViewRowHeight(); 214 gfx::Rect row_bounds(bounds.x(), bounds.bottom() - height, 215 bounds.width(), height); 216 if (cancel_button_) { 217 const gfx::Size size = cancel_button_->GetPreferredSize(); 218 row_bounds.set_width(row_bounds.width() - size.width()); 219 cancel_button_->SetBounds(row_bounds.right(), row_bounds.y(), 220 size.width(), height); 221 row_bounds.set_width(row_bounds.width() - kRelatedButtonHSpacing); 222 } 223 if (ok_button_) { 224 const gfx::Size size = ok_button_->GetPreferredSize(); 225 row_bounds.set_width(row_bounds.width() - size.width()); 226 ok_button_->SetBounds(row_bounds.right(), row_bounds.y(), 227 size.width(), height); 228 row_bounds.set_width(row_bounds.width() - kRelatedButtonHSpacing); 229 } 230 if (extra_view_) { 231 row_bounds.set_width(std::min(row_bounds.width(), 232 extra_view_->GetPreferredSize().width())); 233 extra_view_->SetBoundsRect(row_bounds); 234 } 235 236 if (height > 0) 237 bounds.Inset(0, 0, 0, height + kRelatedControlVerticalSpacing); 238 } 239 240 // Layout the contents view to the top and side edges of the contents bounds. 241 // NOTE: The local insets do not apply to the contents view sides or top. 242 const gfx::Rect contents_bounds = GetContentsBounds(); 243 contents_view()->SetBounds(contents_bounds.x(), contents_bounds.y(), 244 contents_bounds.width(), bounds.bottom() - contents_bounds.y()); 245 } 246 247 bool DialogClientView::AcceleratorPressed(const ui::Accelerator& accelerator) { 248 DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE); 249 Close(); 250 return true; 251 } 252 253 void DialogClientView::ViewHierarchyChanged( 254 const ViewHierarchyChangedDetails& details) { 255 ClientView::ViewHierarchyChanged(details); 256 if (details.is_add && details.child == this) { 257 // The old dialog style needs an explicit background color, while the new 258 // dialog style simply inherits the bubble's frame view color. 259 const DialogDelegate* dialog = GetDialogDelegate(); 260 if (dialog && !dialog->UseNewStyleForThisDialog()) 261 set_background(views::Background::CreateSolidBackground(GetNativeTheme()-> 262 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); 263 264 focus_manager_ = GetFocusManager(); 265 if (focus_manager_) 266 GetFocusManager()->AddFocusChangeListener(this); 267 268 UpdateDialogButtons(); 269 CreateExtraView(); 270 CreateFootnoteView(); 271 } else if (!details.is_add && details.child == this) { 272 if (focus_manager_) 273 focus_manager_->RemoveFocusChangeListener(this); 274 focus_manager_ = NULL; 275 } else if (!details.is_add) { 276 if (details.child == default_button_) 277 default_button_ = NULL; 278 if (details.child == ok_button_) 279 ok_button_ = NULL; 280 if (details.child == cancel_button_) 281 cancel_button_ = NULL; 282 } 283 } 284 285 void DialogClientView::NativeViewHierarchyChanged() { 286 FocusManager* focus_manager = GetFocusManager(); 287 if (focus_manager_ != focus_manager) { 288 if (focus_manager_) 289 focus_manager_->RemoveFocusChangeListener(this); 290 focus_manager_ = focus_manager; 291 if (focus_manager_) 292 focus_manager_->AddFocusChangeListener(this); 293 } 294 } 295 296 //////////////////////////////////////////////////////////////////////////////// 297 // DialogClientView, ButtonListener implementation: 298 299 void DialogClientView::ButtonPressed(Button* sender, const ui::Event& event) { 300 // Check for a valid delegate to avoid handling events after destruction. 301 if (!GetDialogDelegate()) 302 return; 303 304 if (sender == ok_button_) 305 AcceptWindow(); 306 else if (sender == cancel_button_) 307 CancelWindow(); 308 else 309 NOTREACHED(); 310 } 311 312 //////////////////////////////////////////////////////////////////////////////// 313 // DialogClientView, protected: 314 315 DialogClientView::DialogClientView(View* contents_view) 316 : ClientView(NULL, contents_view), 317 ok_button_(NULL), 318 cancel_button_(NULL), 319 default_button_(NULL), 320 focus_manager_(NULL), 321 extra_view_(NULL), 322 footnote_view_(NULL), 323 notified_delegate_(false) {} 324 325 DialogDelegate* DialogClientView::GetDialogDelegate() const { 326 return GetWidget()->widget_delegate()->AsDialogDelegate(); 327 } 328 329 void DialogClientView::CreateExtraView() { 330 if (extra_view_) 331 return; 332 333 extra_view_ = GetDialogDelegate()->CreateExtraView(); 334 if (extra_view_) { 335 extra_view_->SetGroup(kButtonGroup); 336 AddChildView(extra_view_); 337 } 338 } 339 340 void DialogClientView::CreateFootnoteView() { 341 if (footnote_view_) 342 return; 343 344 footnote_view_ = GetDialogDelegate()->CreateFootnoteView(); 345 if (footnote_view_) 346 AddChildView(footnote_view_); 347 } 348 349 void DialogClientView::ChildPreferredSizeChanged(View* child) { 350 if (child == footnote_view_ || child == extra_view_) 351 Layout(); 352 } 353 354 void DialogClientView::ChildVisibilityChanged(View* child) { 355 ChildPreferredSizeChanged(child); 356 } 357 358 //////////////////////////////////////////////////////////////////////////////// 359 // DialogClientView, private: 360 361 LabelButton* DialogClientView::CreateDialogButton(ui::DialogButton type) { 362 const string16 title = GetDialogDelegate()->GetDialogButtonLabel(type); 363 LabelButton* button = NULL; 364 if (GetDialogDelegate()->UseNewStyleForThisDialog() && 365 GetDialogDelegate()->GetDefaultDialogButton() == type && 366 GetDialogDelegate()->ShouldDefaultButtonBeBlue()) { 367 button = new BlueButton(this, title); 368 } else { 369 button = new LabelButton(this, title); 370 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 371 } 372 button->SetFocusable(true); 373 374 const int kDialogMinButtonWidth = 75; 375 button->set_min_size(gfx::Size(kDialogMinButtonWidth, 0)); 376 button->SetGroup(kButtonGroup); 377 return button; 378 } 379 380 void DialogClientView::UpdateButton(LabelButton* button, 381 ui::DialogButton type) { 382 DialogDelegate* dialog = GetDialogDelegate(); 383 button->SetText(dialog->GetDialogButtonLabel(type)); 384 button->SetEnabled(dialog->IsDialogButtonEnabled(type)); 385 386 if (type == dialog->GetDefaultDialogButton()) { 387 default_button_ = button; 388 button->SetIsDefault(true); 389 } 390 } 391 392 int DialogClientView::GetButtonsAndExtraViewRowHeight() const { 393 int extra_view_height = ShouldShow(extra_view_) ? 394 extra_view_->GetPreferredSize().height() : 0; 395 int buttons_height = std::max( 396 ok_button_ ? ok_button_->GetPreferredSize().height() : 0, 397 cancel_button_ ? cancel_button_->GetPreferredSize().height() : 0); 398 return std::max(extra_view_height, buttons_height); 399 } 400 401 gfx::Insets DialogClientView::GetButtonRowInsets() const { 402 // NOTE: The insets only apply to the buttons, extra view, and footnote view. 403 return GetButtonsAndExtraViewRowHeight() == 0 ? gfx::Insets() : 404 gfx::Insets(0, kButtonHEdgeMarginNew, 405 kButtonVEdgeMarginNew, kButtonHEdgeMarginNew); 406 } 407 408 void DialogClientView::Close() { 409 GetWidget()->Close(); 410 GetDialogDelegate()->OnClosed(); 411 } 412 413 } // namespace views 414