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