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).
ShouldShow(View * view)27 bool ShouldShow(View* view) {
28 return view && view->visible();
29 }
30
31 } // namespace
32
33 ///////////////////////////////////////////////////////////////////////////////
34 // DialogClientView, public:
35
DialogClientView(Widget * owner,View * contents_view)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
~DialogClientView()47 DialogClientView::~DialogClientView() {
48 }
49
AcceptWindow()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
CancelWindow()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
UpdateDialogButtons()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
CanClose()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
AsDialogClientView()124 DialogClientView* DialogClientView::AsDialogClientView() {
125 return this;
126 }
127
AsDialogClientView() const128 const DialogClientView* DialogClientView::AsDialogClientView() const {
129 return this;
130 }
131
OnWillChangeFocus(View * focused_before,View * focused_now)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
OnDidChangeFocus(View * focused_before,View * focused_now)153 void DialogClientView::OnDidChangeFocus(View* focused_before,
154 View* focused_now) {
155 }
156
157 ////////////////////////////////////////////////////////////////////////////////
158 // DialogClientView, View overrides:
159
GetPreferredSize()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
Layout()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
AcceleratorPressed(const ui::Accelerator & accelerator)247 bool DialogClientView::AcceleratorPressed(const ui::Accelerator& accelerator) {
248 DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
249 Close();
250 return true;
251 }
252
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)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
NativeViewHierarchyChanged()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
ButtonPressed(Button * sender,const ui::Event & event)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
DialogClientView(View * contents_view)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
GetDialogDelegate() const325 DialogDelegate* DialogClientView::GetDialogDelegate() const {
326 return GetWidget()->widget_delegate()->AsDialogDelegate();
327 }
328
CreateExtraView()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
CreateFootnoteView()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
ChildPreferredSizeChanged(View * child)349 void DialogClientView::ChildPreferredSizeChanged(View* child) {
350 if (child == footnote_view_ || child == extra_view_)
351 Layout();
352 }
353
ChildVisibilityChanged(View * child)354 void DialogClientView::ChildVisibilityChanged(View* child) {
355 ChildPreferredSizeChanged(child);
356 }
357
358 ////////////////////////////////////////////////////////////////////////////////
359 // DialogClientView, private:
360
CreateDialogButton(ui::DialogButton type)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
UpdateButton(LabelButton * button,ui::DialogButton type)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
GetButtonsAndExtraViewRowHeight() const392 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
GetButtonRowInsets() const401 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
Close()408 void DialogClientView::Close() {
409 GetWidget()->Close();
410 GetDialogDelegate()->OnClosed();
411 }
412
413 } // namespace views
414