// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/views/examples/dialog_example.h" #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "ui/views/bubble/bubble_dialog_delegate.h" #include "ui/views/controls/button/checkbox.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/button/md_text_button.h" #include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/label.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/layout/grid_layout.h" #include "ui/views/layout/layout_constants.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/widget.h" #include "ui/views/window/dialog_client_view.h" using base::ASCIIToUTF16; namespace views { namespace examples { namespace { constexpr int kFieldsColumnId = 0; constexpr int kButtonsColumnId = 1; constexpr int kFakeModeless = ui::MODAL_TYPE_SYSTEM + 1; } // namespace template class DialogExample::Delegate : public virtual DialogType { public: explicit Delegate(DialogExample* parent) : parent_(parent) {} void InitDelegate() { LayoutManager* fill_layout = new FillLayout(); this->SetLayoutManager(fill_layout); Label* body = new Label(parent_->body_->text()); body->SetMultiLine(true); body->SetHorizontalAlignment(gfx::ALIGN_LEFT); body->set_background(Background::CreateSolidBackground(0, 255, 255)); this->AddChildView(body); // Give the example code a way to change the body text. parent_->last_body_label_ = body; } protected: // WidgetDelegate: ui::ModalType GetModalType() const override { return parent_->GetModalType(); } base::string16 GetWindowTitle() const override { return parent_->title_->text(); } // DialogDelegate: View* CreateExtraView() { if (!parent_->has_extra_button_->checked()) return nullptr; return MdTextButton::CreateSecondaryUiButton( nullptr, parent_->extra_button_label_->text()); } bool Cancel() override { return parent_->AllowDialogClose(false); } bool Accept() override { return parent_->AllowDialogClose(true); } int GetDialogButtons() const override { return parent_->GetDialogButtons(); } base::string16 GetDialogButtonLabel(ui::DialogButton button) const override { if (button == ui::DIALOG_BUTTON_OK) return parent_->ok_button_label_->text(); if (button == ui::DIALOG_BUTTON_CANCEL) return parent_->cancel_button_label_->text(); return base::string16(); } private: DialogExample* parent_; DISALLOW_COPY_AND_ASSIGN(Delegate); }; class DialogExample::Bubble : public Delegate { public: Bubble(DialogExample* parent, View* anchor) : BubbleDialogDelegateView(anchor, BubbleBorder::TOP_LEFT), Delegate(parent) { set_close_on_deactivate(!parent->persistent_bubble_->checked()); } // BubbleDialogDelegateView: void Init() override { InitDelegate(); } private: DISALLOW_COPY_AND_ASSIGN(Bubble); }; class DialogExample::Dialog : public Delegate { public: explicit Dialog(DialogExample* parent) : Delegate(parent) {} // WidgetDelegate: bool CanResize() const override { // Mac supports resizing of modal dialogs (parent or window-modal). On other // platforms this will be weird unless the modal type is "none", but helps // test layout. return true; } private: DISALLOW_COPY_AND_ASSIGN(Dialog); }; DialogExample::DialogExample() : ExampleBase("Dialog"), mode_model_({ base::ASCIIToUTF16("Modeless"), base::ASCIIToUTF16("Window Modal"), base::ASCIIToUTF16("Child Modal"), base::ASCIIToUTF16("System Modal"), base::ASCIIToUTF16("Fake Modeless (non-bubbles)"), }) {} DialogExample::~DialogExample() {} void DialogExample::CreateExampleView(View* container) { // GridLayout |resize_percent| constants. const float kFixed = 0.f; const float kStretchy = 1.f; const int horizontal_spacing = ViewsDelegate::GetInstance()->GetDialogRelatedButtonHorizontalSpacing(); GridLayout* layout = GridLayout::CreatePanel(container); container->SetLayoutManager(layout); ColumnSet* column_set = layout->AddColumnSet(kFieldsColumnId); column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, kFixed, GridLayout::USE_PREF, 0, 0); column_set->AddPaddingColumn(kFixed, horizontal_spacing); column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, kStretchy, GridLayout::USE_PREF, 0, 0); column_set->AddPaddingColumn(kFixed, horizontal_spacing); column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, kFixed, GridLayout::USE_PREF, 0, 0); StartTextfieldRow(layout, &title_, "Dialog Title", "Title"); StartTextfieldRow(layout, &body_, "Dialog Body Text", "Body Text"); StartTextfieldRow(layout, &ok_button_label_, "OK Button Label", "Done"); AddCheckbox(layout, &has_ok_button_); StartTextfieldRow(layout, &cancel_button_label_, "Cancel Button Label", "Cancel"); AddCheckbox(layout, &has_cancel_button_); StartTextfieldRow(layout, &extra_button_label_, "Extra Button Label", "Edit"); AddCheckbox(layout, &has_extra_button_); StartRowWithLabel(layout, "Modal Type"); mode_ = new Combobox(&mode_model_); mode_->set_listener(this); mode_->SetSelectedIndex(ui::MODAL_TYPE_CHILD); layout->AddView(mode_); StartRowWithLabel(layout, "Bubble"); AddCheckbox(layout, &bubble_); AddCheckbox(layout, &persistent_bubble_); persistent_bubble_->SetText(base::ASCIIToUTF16("Persistent")); column_set = layout->AddColumnSet(kButtonsColumnId); column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, kStretchy, GridLayout::USE_PREF, 0, 0); layout->StartRowWithPadding(kFixed, kButtonsColumnId, kFixed, kUnrelatedControlVerticalSpacing); show_ = MdTextButton::CreateSecondaryUiButton(this, base::ASCIIToUTF16("Show")); layout->AddView(show_); // Grow the dialog a bit when this example is first selected, so it all fits. gfx::Size dialog_size = container->GetWidget()->GetRestoredBounds().size(); dialog_size.set_height(dialog_size.height() + 80); container->GetWidget()->SetSize(dialog_size); } void DialogExample::StartRowWithLabel(GridLayout* layout, const char* label) { const float kFixedVerticalResize = 0.f; layout->StartRowWithPadding( kFixedVerticalResize, kFieldsColumnId, kFixedVerticalResize, ViewsDelegate::GetInstance()->GetDialogRelatedControlVerticalSpacing()); layout->AddView(new Label(base::ASCIIToUTF16(label))); } void DialogExample::StartTextfieldRow(GridLayout* layout, Textfield** member, const char* label, const char* value) { StartRowWithLabel(layout, label); Textfield* textfield = new Textfield(); layout->AddView(textfield); textfield->set_controller(this); textfield->SetText(base::ASCIIToUTF16(value)); *member = textfield; } void DialogExample::AddCheckbox(GridLayout* layout, Checkbox** member) { Checkbox* checkbox = new Checkbox(base::string16()); checkbox->set_listener(this); checkbox->SetChecked(true); layout->AddView(checkbox); *member = checkbox; } ui::ModalType DialogExample::GetModalType() const { // "Fake" modeless happens when a DialogDelegate specifies window-modal, but // doesn't provide a parent window. if (mode_->selected_index() == kFakeModeless) return ui::MODAL_TYPE_WINDOW; return static_cast(mode_->selected_index()); } int DialogExample::GetDialogButtons() const { int buttons = 0; if (has_ok_button_->checked()) buttons |= ui::DIALOG_BUTTON_OK; if (has_cancel_button_->checked()) buttons |= ui::DIALOG_BUTTON_CANCEL; return buttons; } bool DialogExample::AllowDialogClose(bool accept) { PrintStatus("Dialog closed with %s.", accept ? "Accept" : "Cancel"); last_dialog_ = nullptr; last_body_label_ = nullptr; return true; } void DialogExample::ResizeDialog() { DCHECK(last_dialog_); Widget* widget = last_dialog_->GetWidget(); gfx::Rect preferred_bounds(widget->GetRestoredBounds()); preferred_bounds.set_size(widget->non_client_view()->GetPreferredSize()); // Q: Do we need NonClientFrameView::GetWindowBoundsForClientBounds() here? // A: When DialogCientView properly feeds back sizes, we do not. widget->SetBoundsConstrained(preferred_bounds); } void DialogExample::ButtonPressed(Button* sender, const ui::Event& event) { if (sender == show_) { if (bubble_->checked()) { Bubble* bubble = new Bubble(this, sender); last_dialog_ = bubble; BubbleDialogDelegateView::CreateBubble(bubble); } else { Dialog* dialog = new Dialog(this); last_dialog_ = dialog; dialog->InitDelegate(); // constrained_window::CreateBrowserModalDialogViews() allows dialogs to // be created as MODAL_TYPE_WINDOW without specifying a parent. gfx::NativeView parent = nullptr; if (mode_->selected_index() != kFakeModeless) parent = container()->GetWidget()->GetNativeView(); DialogDelegate::CreateDialogWidget( dialog, container()->GetWidget()->GetNativeWindow(), parent); } last_dialog_->GetWidget()->Show(); return; } if (sender == bubble_) { if (bubble_->checked() && GetModalType() != ui::MODAL_TYPE_CHILD) { mode_->SetSelectedIndex(ui::MODAL_TYPE_CHILD); PrintStatus("You nearly always want Child Modal for bubbles."); } persistent_bubble_->SetEnabled(bubble_->checked()); OnPerformAction(mode_); // Validate the modal type. if (!bubble_->checked() && GetModalType() == ui::MODAL_TYPE_CHILD) { // Do something reasonable when simply unchecking bubble and re-enable. mode_->SetSelectedIndex(ui::MODAL_TYPE_WINDOW); OnPerformAction(mode_); } return; } // Other buttons are all checkboxes. Update the dialog if there is one. if (last_dialog_) { last_dialog_->GetDialogClientView()->UpdateDialogButtons(); ResizeDialog(); } } void DialogExample::ContentsChanged(Textfield* sender, const base::string16& new_contents) { if (!last_dialog_) return; if (sender == extra_button_label_) PrintStatus("DialogClientView can never refresh the extra view."); if (sender == title_) { last_dialog_->GetWidget()->UpdateWindowTitle(); } else if (sender == body_) { last_body_label_->SetText(new_contents); } else { last_dialog_->GetDialogClientView()->UpdateDialogButtons(); } ResizeDialog(); } void DialogExample::OnPerformAction(Combobox* combobox) { bool enable = bubble_->checked() || GetModalType() != ui::MODAL_TYPE_CHILD; #if defined(OS_MACOSX) enable = enable && GetModalType() != ui::MODAL_TYPE_SYSTEM; #endif show_->SetEnabled(enable); if (!enable && GetModalType() == ui::MODAL_TYPE_CHILD) PrintStatus("MODAL_TYPE_CHILD can't be used with non-bubbles."); if (!enable && GetModalType() == ui::MODAL_TYPE_SYSTEM) PrintStatus("MODAL_TYPE_SYSTEM isn't supported on Mac."); } } // namespace examples } // namespace views