Commit 5b78418f authored by Peter Boström's avatar Peter Boström Committed by Commit Bot

Add a model-based dialog framework

Introduces a formal split between a dialog model and the bubble in which
it's hosted. Refactors BookmarkBubbleView to use it.

Several DCHECKs are in place and TODOs that need to be resolved while
converting more dialogs to make this more flexible.

Bug: 1106422
Change-Id: Ied4baeaf3237f22f770562dc3161030e8640354d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2292700
Commit-Queue: Peter Boström <pbos@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797105}
parent d3395285
...@@ -5,18 +5,15 @@ ...@@ -5,18 +5,15 @@
#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h" #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/platform_util.h" #include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_bubble_observer.h" #include "chrome/browser/ui/bookmarks/bookmark_bubble_observer.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h" #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h"
#include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h" #include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/textfield_layout.h"
#include "chrome/grit/chromium_strings.h" #include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_model.h"
...@@ -24,13 +21,8 @@ ...@@ -24,13 +21,8 @@
#include "components/signin/public/base/signin_metrics.h" #include "components/signin/public/base/signin_metrics.h"
#include "components/strings/grit/components_strings.h" #include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h" #include "ui/base/models/dialog_model.h"
#include "ui/views/controls/button/md_text_button.h" #include "ui/views/bubble/bubble_dialog_model_host.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/widget/widget.h"
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h" #include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
...@@ -40,248 +32,236 @@ using base::UserMetricsAction; ...@@ -40,248 +32,236 @@ using base::UserMetricsAction;
using bookmarks::BookmarkModel; using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode; using bookmarks::BookmarkNode;
BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = nullptr; // TODO(pbos): Investigate replacing this with a views-agnostic
// BookmarkBubbleDelegate.
views::BubbleDialogDelegate* BookmarkBubbleView::bookmark_bubble_ = nullptr;
namespace { namespace {
constexpr int kBookmarkName = 1;
std::unique_ptr<views::View> CreateEditButton(views::ButtonListener* listener) { constexpr int kBookmarkFolder = 2;
auto edit_button = std::make_unique<views::MdTextButton>(
listener, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
edit_button->AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN));
return edit_button;
} }
} // namespace class BookmarkBubbleView::BookmarkBubbleDelegate
: public ui::DialogModelDelegate {
// static public:
views::Widget* BookmarkBubbleView::ShowBubble( BookmarkBubbleDelegate(bookmarks::BookmarkBubbleObserver* observer,
views::View* anchor_view,
views::Button* highlighted_button,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate, std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile, Profile* profile,
const GURL& url, const GURL& url)
bool already_bookmarked) { : observer_(observer),
if (bookmark_bubble_) delegate_(std::move(delegate)),
return nullptr; profile_(profile),
url_(url) {}
bookmark_bubble_ =
new BookmarkBubbleView(anchor_view, observer, std::move(delegate),
profile, url, !already_bookmarked);
if (highlighted_button)
bookmark_bubble_->SetHighlightedButton(highlighted_button);
views::Widget* bubble_widget =
views::BubbleDialogDelegateView::CreateBubble(bookmark_bubble_);
bubble_widget->Show();
// Select the entire title textfield contents when the bubble is first shown.
bookmark_bubble_->name_field_->SelectAll(true);
if (bookmark_bubble_->observer_) {
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
bookmark_bubble_->observer_->OnBookmarkBubbleShown(node);
}
return bubble_widget;
}
// static
void BookmarkBubbleView::Hide() {
if (bookmark_bubble_)
bookmark_bubble_->GetWidget()->Close();
}
BookmarkBubbleView::~BookmarkBubbleView() { void RemoveBookmark() {
if (apply_edits_) { base::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
ApplyEdits(); can_apply_edits_ = false;
} else if (remove_bookmark_) { bookmarks::BookmarkModel* model =
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_); BookmarkModelFactory::GetForBrowserContext(profile_);
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_); const bookmarks::BookmarkNode* node =
model->GetMostRecentlyAddedUserNodeForURL(url_);
if (node) if (node)
model->Remove(node); model->Remove(node);
} }
}
// views::WidgetDelegate -------------------------------------------------------
views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
return name_field_;
}
void BookmarkBubbleView::WindowClosing() {
// We have to reset |bubble_| here, not in our destructor, because we'll be
// destroyed asynchronously and the shown state will be checked before then.
DCHECK_EQ(bookmark_bubble_, this);
bookmark_bubble_ = NULL;
void OnWindowClosing() {
bookmark_bubble_ = nullptr;
if (observer_) if (observer_)
observer_->OnBookmarkBubbleHidden(); observer_->OnBookmarkBubbleHidden();
} }
// views::DialogDelegate -------------------------------------------------------
void BookmarkBubbleView::OnDialogCancelled() {
base::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
// Set this so we remove the bookmark after the window closes.
remove_bookmark_ = true;
apply_edits_ = false;
}
void BookmarkBubbleView::OnDialogInitialized() {
views::Button* cancel = GetCancelButton();
if (cancel)
cancel->AddAccelerator(ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN));
}
// views::ButtonListener -------------------------------------------------------
void BookmarkBubbleView::ButtonPressed(views::Button* sender, void OnEditButton(const ui::Event& event) {
const ui::Event& event) {
base::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); base::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
ShowEditor(); ShowEditor();
}
// views::ComboboxListener -----------------------------------------------------
void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
if (combobox->GetSelectedIndex() + 1 == folder_model()->GetItemCount()) {
base::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
ShowEditor();
} }
}
// views::BubbleDialogDelegateView ---------------------------------------------
void BookmarkBubbleView::Init() {
SetLayoutManager(std::make_unique<views::FillLayout>());
auto bookmark_contents_view = std::make_unique<views::View>();
views::GridLayout* layout = bookmark_contents_view->SetLayoutManager(
std::make_unique<views::GridLayout>());
constexpr int kColumnId = 0;
ConfigureTextfieldStack(layout, kColumnId);
name_field_ = AddFirstTextfieldRow(
layout, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_NAME_LABEL),
kColumnId);
name_field_->SetText(GetBookmarkName());
name_field_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_NAME_LABEL));
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_);
auto parent_folder_model = std::make_unique<RecentlyUsedFoldersComboModel>(
model, model->GetMostRecentlyAddedUserNodeForURL(url_));
parent_combobox_ = AddComboboxRow(
layout, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_LABEL),
std::move(parent_folder_model), kColumnId);
parent_combobox_->set_listener(this);
parent_combobox_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_LABEL));
bookmark_contents_view_ = AddChildView(std::move(bookmark_contents_view));
}
// Private methods -------------------------------------------------------------
BookmarkBubbleView::BookmarkBubbleView(
views::View* anchor_view,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile,
const GURL& url,
bool newly_bookmarked)
: LocationBarBubbleDelegateView(anchor_view, nullptr),
observer_(observer),
delegate_(std::move(delegate)),
profile_(profile),
url_(url) {
DCHECK(anchor_view);
WidgetDelegate::SetTitle(l10n_util::GetStringUTF16(
newly_bookmarked ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED
: IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
WidgetDelegate::SetShowCloseButton(true);
SetArrow(views::BubbleBorder::TOP_RIGHT);
SetButtonLabel(ui::DIALOG_BUTTON_OK, l10n_util::GetStringUTF16(IDS_DONE));
SetButtonLabel(
ui::DIALOG_BUTTON_CANCEL,
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
SetExtraView(CreateEditButton(this));
SetFootnoteView(CreateSigninPromoView());
SetCancelCallback(base::BindOnce(&BookmarkBubbleView::OnDialogCancelled,
base::Unretained(this)));
chrome::RecordDialogCreation(chrome::DialogIdentifier::BOOKMARK);
set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
views::CONTROL, views::CONTROL));
}
base::string16 BookmarkBubbleView::GetBookmarkName() {
BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile_);
const BookmarkNode* node =
bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
if (node)
return node->GetTitle();
else
NOTREACHED();
return base::string16();
}
void BookmarkBubbleView::ShowEditor() { void ShowEditor() {
const BookmarkNode* node = DCHECK(dialog_model()->host());
const bookmarks::BookmarkNode* node =
BookmarkModelFactory::GetForBrowserContext(profile_) BookmarkModelFactory::GetForBrowserContext(profile_)
->GetMostRecentlyAddedUserNodeForURL(url_); ->GetMostRecentlyAddedUserNodeForURL(url_);
DCHECK(bookmark_bubble_->anchor_widget());
gfx::NativeWindow native_parent = gfx::NativeWindow native_parent =
anchor_widget() ? anchor_widget()->GetNativeWindow() bookmark_bubble_->anchor_widget()->GetNativeWindow();
: platform_util::GetTopLevel(parent_window());
DCHECK(native_parent); DCHECK(native_parent);
Profile* profile = profile_; Profile* const profile = profile_;
ApplyEdits(); ApplyEdits();
GetWidget()->Close(); dialog_model()->host()->Close();
if (node && native_parent) if (node && native_parent) {
BookmarkEditor::Show(native_parent, profile, BookmarkEditor::Show(native_parent, profile,
BookmarkEditor::EditDetails::EditNode(node), BookmarkEditor::EditDetails::EditNode(node),
BookmarkEditor::SHOW_TREE); BookmarkEditor::SHOW_TREE);
} }
}
void OnComboboxAction() {
if (dialog_model()
->GetComboboxByUniqueId(kBookmarkFolder)
->selected_index() +
1 ==
GetFolderModel()->GetItemCount()) {
base::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
ShowEditor();
}
}
void BookmarkBubbleView::ApplyEdits() { void ApplyEdits() {
DCHECK(can_apply_edits_);
// Set this to make sure we don't attempt to apply edits again. // Set this to make sure we don't attempt to apply edits again.
apply_edits_ = false; can_apply_edits_ = false;
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_); bookmarks::BookmarkModel* const model =
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_); BookmarkModelFactory::GetForBrowserContext(profile_);
if (node) { const bookmarks::BookmarkNode* node =
const base::string16 new_title = name_field_->GetText(); model->GetMostRecentlyAddedUserNodeForURL(url_);
if (!node)
return;
const base::string16 new_title =
dialog_model()->GetTextfieldByUniqueId(kBookmarkName)->text();
if (new_title != node->GetTitle()) { if (new_title != node->GetTitle()) {
model->SetTitle(node, new_title); model->SetTitle(node, new_title);
base::RecordAction( base::RecordAction(
UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
} }
folder_model()->MaybeChangeParent(node,
parent_combobox_->GetSelectedIndex()); GetFolderModel()->MaybeChangeParent(
node, dialog_model()
->GetComboboxByUniqueId(kBookmarkFolder)
->selected_index());
} }
}
std::unique_ptr<views::View> BookmarkBubbleView::CreateSigninPromoView() { RecentlyUsedFoldersComboModel* GetFolderModel() {
#if defined(OS_CHROMEOS) DCHECK(dialog_model());
// ChromeOS does not show the signin promo. return static_cast<RecentlyUsedFoldersComboModel*>(
return nullptr; dialog_model()
#else ->GetComboboxByUniqueId(kBookmarkFolder)
if (!SyncPromoUI::ShouldShowSyncPromo(profile_)) ->combobox_model());
return nullptr; }
BubbleSyncPromoDelegate* delegate() { return delegate_.get(); }
private:
bookmarks::BookmarkBubbleObserver* const observer_;
std::unique_ptr<BubbleSyncPromoDelegate> delegate_;
Profile* const profile_;
const GURL url_;
bool can_apply_edits_ = true;
};
// static
void BookmarkBubbleView::ShowBubble(
views::View* anchor_view,
views::Button* highlighted_button,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile,
const GURL& url,
bool already_bookmarked) {
if (bookmark_bubble_)
return;
#if !defined(OS_CHROMEOS)
BubbleSyncPromoDelegate* const delegate_ptr = delegate.get();
#endif // !defined(OS_CHROMEOS)
bookmarks::BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile);
const bookmarks::BookmarkNode* bookmark_node =
bookmark_model->GetMostRecentlyAddedUserNodeForURL(url);
auto bubble_delegate_unique = std::make_unique<BookmarkBubbleDelegate>(
observer, std::move(delegate), profile, url);
BookmarkBubbleDelegate* bubble_delegate = bubble_delegate_unique.get();
auto dialog_model =
ui::DialogModel::Builder(std::move(bubble_delegate_unique))
.SetShowCloseButton(true)
.SetTitle(l10n_util::GetStringUTF16(
already_bookmarked ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK
: IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED))
.SetAcceptCallback(base::BindOnce(&BookmarkBubbleDelegate::ApplyEdits,
base::Unretained(bubble_delegate)))
.SetCancelCallback(
base::BindOnce(&BookmarkBubbleDelegate::RemoveBookmark,
base::Unretained(bubble_delegate)))
.SetWindowClosingCallback(
base::BindOnce(&BookmarkBubbleDelegate::OnWindowClosing,
base::Unretained(bubble_delegate)))
.AddDialogButton(ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(IDS_DONE))
.AddDialogButton(
ui::DIALOG_BUTTON_CANCEL,
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK),
ui::DialogModelButton::Params().AddAccelerator(
ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN)))
.AddDialogExtraButton(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS),
ui::DialogModelButton::Params()
.SetCallback(
base::BindRepeating(&BookmarkBubbleDelegate::OnEditButton,
base::Unretained(bubble_delegate)))
.AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN)))
.AddTextfield(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_NAME_LABEL),
bookmark_node->GetTitle(),
ui::DialogModelTextfield::Params()
.SetUniqueId(kBookmarkName)
.SetAccessibleName(l10n_util::GetStringUTF16(
IDS_BOOKMARK_AX_BUBBLE_NAME_LABEL)))
.AddCombobox(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_LABEL),
std::make_unique<RecentlyUsedFoldersComboModel>(
bookmark_model,
bookmark_model->GetMostRecentlyAddedUserNodeForURL(url)),
ui::DialogModelCombobox::Params()
.SetUniqueId(kBookmarkFolder)
.SetCallback(base::BindRepeating(
&BookmarkBubbleDelegate::OnComboboxAction,
base::Unretained(bubble_delegate))))
.SetInitiallyFocusedField(kBookmarkName)
.Build();
// views:: land below, there's no agnostic reference to arrow / anchors /
// bubbles.
auto bubble =
std::make_unique<views::BubbleDialogModelHost>(std::move(dialog_model));
bubble->SelectAllText(kBookmarkName);
bookmark_bubble_ = bubble.get();
bubble->SetAnchorView(anchor_view);
bubble->SetArrow(views::BubbleBorder::TOP_RIGHT);
if (highlighted_button)
bubble->SetHighlightedButton(highlighted_button);
return std::make_unique<DiceBubbleSyncPromoView>( #if !defined(OS_CHROMEOS)
profile_, delegate_.get(), if (SyncPromoUI::ShouldShowSyncPromo(profile)) {
// TODO(pbos): Consider adding model support for footnotes so that this does
// not need to be tied to views.
// TODO(pbos): Consider updating ::SetFootnoteView so that it can resize the
// widget to account for it.
bubble->SetFootnoteView(std::make_unique<DiceBubbleSyncPromoView>(
profile, delegate_ptr,
signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE, signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
IDS_BOOKMARK_DICE_PROMO_SYNC_MESSAGE, IDS_BOOKMARK_DICE_PROMO_SYNC_MESSAGE,
/*dice_signin_button_prominent=*/false); /*dice_signin_button_prominent=*/false));
}
#endif #endif
views::Widget* const widget =
views::BubbleDialogDelegateView::CreateBubble(bubble.release());
widget->Show();
chrome::RecordDialogCreation(chrome::DialogIdentifier::BOOKMARK);
if (observer) {
observer->OnBookmarkBubbleShown(
BookmarkModelFactory::GetForBrowserContext(profile)
->GetMostRecentlyAddedUserNodeForURL(url));
}
} }
BEGIN_METADATA(BookmarkBubbleView) // static
METADATA_PARENT_CLASS(LocationBarBubbleDelegateView); void BookmarkBubbleView::Hide() {
END_METADATA() if (bookmark_bubble_)
bookmark_bubble_->GetWidget()->Close();
}
...@@ -8,15 +8,9 @@ ...@@ -8,15 +8,9 @@
#include <memory> #include <memory>
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/string16.h"
#include "chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h"
#include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h" #include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "url/gurl.h"
class GURL;
class Profile; class Profile;
namespace bookmarks { namespace bookmarks {
...@@ -24,25 +18,16 @@ class BookmarkBubbleObserver; ...@@ -24,25 +18,16 @@ class BookmarkBubbleObserver;
} }
namespace views { namespace views {
class Textfield; class BubbleDialogDelegate;
class Button;
class View;
} }
// BookmarkBubbleView is a view intended to be used as the content of an // BookmarkBubbleView provides a dialog for unstarring and editing the bookmark
// Bubble. BookmarkBubbleView provides views for unstarring and editing the // it is created with. The dialog is created using the static ShowBubble method.
// bookmark it is created with. Don't create a BookmarkBubbleView directly, class BookmarkBubbleView {
// instead use the static Show method.
class BookmarkBubbleView : public LocationBarBubbleDelegateView,
public views::ButtonListener,
public views::ComboboxListener {
public: public:
METADATA_HEADER(BookmarkBubbleView); static void ShowBubble(views::View* anchor_view,
// If |anchor_view| is null, |anchor_rect| is used to anchor the bubble and
// |parent_window| is used to ensure the bubble closes if the parent closes.
// Returns the newly created bubble's Widget or nullptr in case when the
// bubble already exists.
static views::Widget* ShowBubble(
views::View* anchor_view,
views::Button* highlighted_button, views::Button* highlighted_button,
bookmarks::BookmarkBubbleObserver* observer, bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate, std::unique_ptr<BubbleSyncPromoDelegate> delegate,
...@@ -52,88 +37,14 @@ class BookmarkBubbleView : public LocationBarBubbleDelegateView, ...@@ -52,88 +37,14 @@ class BookmarkBubbleView : public LocationBarBubbleDelegateView,
static void Hide(); static void Hide();
static BookmarkBubbleView* bookmark_bubble() { return bookmark_bubble_; } static views::BubbleDialogDelegate* bookmark_bubble() {
return bookmark_bubble_;
~BookmarkBubbleView() override;
// LocationBarBubbleDelegateView:
View* GetInitiallyFocusedView() override;
void WindowClosing() override;
void OnDialogInitialized() override;
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::ComboboxListener:
void OnPerformAction(views::Combobox* combobox) override;
protected:
// LocationBarBubbleDelegateView:
void Init() override;
private:
friend class BookmarkBubbleViewTest;
friend class BookmarkBubbleViewBrowserTest;
// Creates a BookmarkBubbleView.
BookmarkBubbleView(views::View* anchor_view,
bookmarks::BookmarkBubbleObserver* observer,
std::unique_ptr<BubbleSyncPromoDelegate> delegate,
Profile* profile,
const GURL& url,
bool newly_bookmarked);
// Returns the name of the bookmark.
base::string16 GetBookmarkName();
// Returns the model used to initialize |parent_combobox_|.
RecentlyUsedFoldersComboModel* folder_model() {
return static_cast<RecentlyUsedFoldersComboModel*>(
parent_combobox_->model());
} }
// Shows the BookmarkEditor. private:
void ShowEditor(); class BookmarkBubbleDelegate;
// Sets the bookmark name and parent of the node.
void ApplyEdits();
// Creates the signin promo view, if there should be one.
std::unique_ptr<views::View> CreateSigninPromoView();
void OnDialogCancelled();
// The bookmark bubble, if we're showing one. // The bookmark bubble, if we're showing one.
static BookmarkBubbleView* bookmark_bubble_; static views::BubbleDialogDelegate* bookmark_bubble_;
// Our observer, to notify when the bubble shows or hides.
bookmarks::BookmarkBubbleObserver* observer_;
// Delegate, to handle clicks on the sign in link.
std::unique_ptr<BubbleSyncPromoDelegate> delegate_;
// The profile.
Profile* profile_;
// The bookmark URL.
const GURL url_;
// Textfield showing the name of the bookmark.
views::Textfield* name_field_ = nullptr;
// Combobox showing a handful of folders the user can choose from, including
// the current parent.
views::Combobox* parent_combobox_ = nullptr;
// The regular bookmark bubble contents, with all the edit fields and dialog
// buttons. TODO(tapted): Move the buttons to the DialogClientView.
views::View* bookmark_contents_view_ = nullptr;
// When the destructor is invoked should the bookmark be removed?
bool remove_bookmark_ = false;
// When the destructor is invoked should edits be applied?
bool apply_edits_ = true;
DISALLOW_COPY_AND_ASSIGN(BookmarkBubbleView); DISALLOW_COPY_AND_ASSIGN(BookmarkBubbleView);
}; };
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "components/bookmarks/test/bookmark_test_helpers.h" #include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/signin/public/identity_manager/identity_test_utils.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
class BookmarkBubbleViewBrowserTest : public DialogBrowserTest { class BookmarkBubbleViewBrowserTest : public DialogBrowserTest {
public: public:
......
...@@ -15,9 +15,13 @@ ...@@ -15,9 +15,13 @@
#include "chrome/browser/signin/identity_manager_factory.h" #include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h" #include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
#include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/views/chrome_test_widget.h"
#include "components/bookmarks/browser/bookmark_utils.h" #include "components/bookmarks/browser/bookmark_utils.h"
#include "components/bookmarks/test/bookmark_test_helpers.h" #include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/signin/public/identity_manager/identity_test_utils.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/unique_widget_ptr.h"
using bookmarks::BookmarkModel; using bookmarks::BookmarkModel;
...@@ -39,6 +43,12 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest { ...@@ -39,6 +43,12 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest {
void SetUp() override { void SetUp() override {
BrowserWithTestWindowTest::SetUp(); BrowserWithTestWindowTest::SetUp();
anchor_widget_ =
views::UniqueWidgetPtr(std::make_unique<ChromeTestWidget>());
views::Widget::InitParams widget_params;
widget_params.context = GetContext();
anchor_widget_->Init(std::move(widget_params));
BookmarkModel* bookmark_model = BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile()); BookmarkModelFactory::GetForBrowserContext(profile());
bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model); bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);
...@@ -49,7 +59,12 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest { ...@@ -49,7 +59,12 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest {
void TearDown() override { void TearDown() override {
// Make sure the bubble is destroyed before the profile to avoid a crash. // Make sure the bubble is destroyed before the profile to avoid a crash.
bubble_.reset(); views::test::WidgetDestroyedWaiter destroyed_waiter(
BookmarkBubbleView::bookmark_bubble()->GetWidget());
BookmarkBubbleView::bookmark_bubble()->GetWidget()->Close();
destroyed_waiter.Wait();
anchor_widget_.reset();
BrowserWithTestWindowTest::TearDown(); BrowserWithTestWindowTest::TearDown();
} }
...@@ -63,18 +78,13 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest { ...@@ -63,18 +78,13 @@ class BookmarkBubbleViewTest : public BrowserWithTestWindowTest {
// Creates a bookmark bubble view. // Creates a bookmark bubble view.
void CreateBubbleView() { void CreateBubbleView() {
// Create a fake anchor view for the bubble. // Create a fake anchor view for the bubble.
anchor_ = std::make_unique<views::View>(); BookmarkBubbleView::ShowBubble(anchor_widget_->GetContentsView(), nullptr,
nullptr, nullptr, profile(),
bubble_.reset(new BookmarkBubbleView(anchor_.get(), nullptr, nullptr, GURL(kTestBookmarkURL), true);
profile(), GURL(kTestBookmarkURL),
true));
bubble_->Init();
} }
std::unique_ptr<BookmarkBubbleView> bubble_;
private: private:
std::unique_ptr<views::View> anchor_; views::UniqueWidgetPtr anchor_widget_;
DISALLOW_COPY_AND_ASSIGN(BookmarkBubbleViewTest); DISALLOW_COPY_AND_ASSIGN(BookmarkBubbleViewTest);
}; };
...@@ -85,13 +95,15 @@ TEST_F(BookmarkBubbleViewTest, SyncPromoSignedIn) { ...@@ -85,13 +95,15 @@ TEST_F(BookmarkBubbleViewTest, SyncPromoSignedIn) {
IdentityManagerFactory::GetForProfile(profile()), IdentityManagerFactory::GetForProfile(profile()),
"fake_username@gmail.com"); "fake_username@gmail.com");
CreateBubbleView(); CreateBubbleView();
EXPECT_FALSE(bubble_->GetFootnoteViewForTesting()); EXPECT_FALSE(
BookmarkBubbleView::bookmark_bubble()->GetFootnoteViewForTesting());
} }
// Verifies that the sync promo is displayed for a user that is not signed in. // Verifies that the sync promo is displayed for a user that is not signed in.
TEST_F(BookmarkBubbleViewTest, SyncPromoNotSignedIn) { TEST_F(BookmarkBubbleViewTest, SyncPromoNotSignedIn) {
CreateBubbleView(); CreateBubbleView();
views::View* footnote = bubble_->GetFootnoteViewForTesting(); views::View* footnote =
BookmarkBubbleView::bookmark_bubble()->GetFootnoteViewForTesting();
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
EXPECT_FALSE(footnote); EXPECT_FALSE(footnote);
#else // !defined(OS_CHROMEOS) #else // !defined(OS_CHROMEOS)
......
...@@ -118,6 +118,11 @@ component("base") { ...@@ -118,6 +118,11 @@ component("base") {
"models/combobox_model.cc", "models/combobox_model.cc",
"models/combobox_model.h", "models/combobox_model.h",
"models/combobox_model_observer.h", "models/combobox_model_observer.h",
"models/dialog_model.cc",
"models/dialog_model.h",
"models/dialog_model_field.cc",
"models/dialog_model_field.h",
"models/dialog_model_host.h",
"models/image_model.cc", "models/image_model.cc",
"models/image_model.h", "models/image_model.h",
"models/list_model.h", "models/list_model.h",
...@@ -436,6 +441,7 @@ component("base") { ...@@ -436,6 +441,7 @@ component("base") {
"//base:base_static", "//base:base_static",
"//base:i18n", "//base:i18n",
"//base/third_party/dynamic_annotations", "//base/third_party/dynamic_annotations",
"//base/util/type_safety:type_safety",
"//net", "//net",
"//third_party/brotli:dec", "//third_party/brotli:dec",
"//third_party/icu", "//third_party/icu",
......
// Copyright 2020 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/base/models/dialog_model.h"
namespace ui {
DialogModel::Builder::Builder(std::unique_ptr<DialogModelDelegate> delegate)
: model_(std::make_unique<DialogModel>(util::PassKey<Builder>(),
std::move(delegate))) {}
DialogModel::Builder::~Builder() {
DCHECK(!model_) << "Model should've been built.";
}
std::unique_ptr<DialogModel> DialogModel::Builder::Build() {
DCHECK(model_);
return std::move(model_);
}
DialogModel::Builder& DialogModel::Builder::SetShowCloseButton(
bool show_close_button) {
model_->show_close_button_ = show_close_button;
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetTitle(base::string16 title) {
model_->title_ = std::move(title);
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetAcceptCallback(
base::OnceClosure callback) {
model_->accept_callback_ = std::move(callback);
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetCancelCallback(
base::OnceClosure callback) {
model_->cancel_callback_ = std::move(callback);
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetCloseCallback(
base::OnceClosure callback) {
model_->close_callback_ = std::move(callback);
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetWindowClosingCallback(
base::OnceClosure callback) {
model_->window_closing_callback_ = std::move(callback);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddDialogButton(
DialogButton button,
base::string16 label,
const DialogModelButton::Params& params) {
model_->AddDialogButton(button, std::move(label), params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddDialogExtraButton(
base::string16 label,
const DialogModelButton::Params& params) {
model_->AddDialogButton(kExtraButtonId, std::move(label), params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddTextfield(
base::string16 label,
base::string16 text,
const DialogModelTextfield::Params& params) {
model_->AddTextfield(std::move(label), std::move(text), params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddCombobox(
base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const DialogModelCombobox::Params& params) {
model_->AddCombobox(std::move(label), std::move(combobox_model), params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetInitiallyFocusedField(
int unique_id) {
// This must be called with unique_id >= 0 (-1 is "no ID").
DCHECK_GE(unique_id, 0);
// This can only be called once.
DCHECK(!model_->initially_focused_field_);
model_->initially_focused_field_ = unique_id;
return *this;
}
DialogModel::DialogModel(util::PassKey<Builder>,
std::unique_ptr<DialogModelDelegate> delegate)
: delegate_(std::move(delegate)) {
delegate_->set_dialog_model(this);
}
DialogModel::~DialogModel() = default;
void DialogModel::AddTextfield(base::string16 label,
base::string16 text,
const DialogModelTextfield::Params& params) {
fields_.push_back(std::make_unique<DialogModelTextfield>(
ReserveField(), std::move(label), std::move(text), params));
if (host_)
host_->OnModelChanged(this);
}
void DialogModel::AddCombobox(base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const DialogModelCombobox::Params& params) {
fields_.push_back(std::make_unique<DialogModelCombobox>(
ReserveField(), std::move(label), std::move(combobox_model), params));
if (host_)
host_->OnModelChanged(this);
}
DialogModelField* DialogModel::GetFieldByUniqueId(int unique_id) {
for (auto& field : fields_) {
if (field->unique_id_ == unique_id)
return field.get();
}
NOTREACHED();
return nullptr;
}
DialogModelButton* DialogModel::GetButtonByUniqueId(int unique_id) {
auto* field = GetFieldByUniqueId(unique_id);
DCHECK_EQ(field->type_, DialogModelField::kButton);
return static_cast<DialogModelButton*>(field);
}
DialogModelCombobox* DialogModel::GetComboboxByUniqueId(int unique_id) {
auto* field = GetFieldByUniqueId(unique_id);
DCHECK_EQ(field->type_, DialogModelField::kCombobox);
return static_cast<DialogModelCombobox*>(field);
}
DialogModelTextfield* DialogModel::GetTextfieldByUniqueId(int unique_id) {
auto* field = GetFieldByUniqueId(unique_id);
DCHECK_EQ(field->type_, DialogModelField::kTextfield);
return static_cast<DialogModelTextfield*>(field);
}
DialogModelButton* DialogModel::GetDialogButton(DialogButton button) {
return GetButtonFromModelFieldId(button);
}
DialogModelButton* DialogModel::GetExtraButton() {
return GetButtonFromModelFieldId(kExtraButtonId);
}
void DialogModel::OnButtonPressed(util::PassKey<DialogModelHost>,
int id,
const Event& event) {
DCHECK_GT(id, DIALOG_BUTTON_LAST);
auto* button = GetButtonFromModelFieldId(id);
if (button->callback_)
button->callback_.Run(event);
}
void DialogModel::OnDialogAccepted(util::PassKey<DialogModelHost>) {
if (accept_callback_)
std::move(accept_callback_).Run();
}
void DialogModel::OnDialogCancelled(util::PassKey<DialogModelHost>) {
if (cancel_callback_)
std::move(cancel_callback_).Run();
}
void DialogModel::OnDialogClosed(util::PassKey<DialogModelHost>) {
if (close_callback_)
std::move(close_callback_).Run();
}
void DialogModel::OnComboboxSelectedIndexChanged(util::PassKey<DialogModelHost>,
int id,
int index) {
GetComboboxFromModelFieldId(id)->selected_index_ = index;
}
void DialogModel::OnComboboxPerformAction(util::PassKey<DialogModelHost>,
int id) {
auto* model = GetComboboxFromModelFieldId(id);
if (model->callback_)
model->callback_.Run();
}
void DialogModel::OnTextfieldTextChanged(util::PassKey<DialogModelHost>,
int id,
base::string16 text) {
GetTextfieldFromModelFieldId(id)->text_ = text;
}
void DialogModel::OnWindowClosing(util::PassKey<DialogModelHost>) {
if (window_closing_callback_)
std::move(window_closing_callback_).Run();
}
void DialogModel::AddDialogButton(int button,
base::string16 label,
const DialogModelButton::Params& params) {
DCHECK_LE(button, kExtraButtonId);
if (button != kExtraButtonId) // Dialog buttons should use dialog callbacks.
DCHECK(!params.has_callback());
DCHECK(!host_); // Dialog buttons should be added before adding to host.
DCHECK(!GetFieldFromModelFieldId(button));
fields_.push_back(std::make_unique<DialogModelButton>(
DialogModelField::Reservation(this, button), std::move(label), params));
}
DialogModelField* DialogModel::GetFieldFromModelFieldId(int id) {
for (const auto& field : fields_) {
if (id == field->model_field_id_)
return field.get();
}
return nullptr;
}
DialogModelButton* DialogModel::GetButtonFromModelFieldId(int id) {
auto* field = GetFieldFromModelFieldId(id);
DCHECK(field);
DCHECK_EQ(field->type_, DialogModelField::kButton);
return static_cast<DialogModelButton*>(field);
}
DialogModelCombobox* DialogModel::GetComboboxFromModelFieldId(int id) {
auto* field = GetFieldFromModelFieldId(id);
DCHECK(field);
DCHECK_EQ(field->type_, DialogModelField::kCombobox);
return static_cast<DialogModelCombobox*>(field);
}
DialogModelTextfield* DialogModel::GetTextfieldFromModelFieldId(int id) {
auto* field = GetFieldFromModelFieldId(id);
DCHECK(field);
DCHECK_EQ(field->type_, DialogModelField::kTextfield);
return static_cast<DialogModelTextfield*>(field);
}
DialogModelField::Reservation DialogModel::ReserveField() {
const int id = next_field_id_++;
DCHECK(!GetFieldFromModelFieldId(id));
return DialogModelField::Reservation(this, id);
}
} // namespace ui
\ No newline at end of file
// Copyright 2020 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.
#ifndef UI_BASE_MODELS_DIALOG_MODEL_H_
#define UI_BASE_MODELS_DIALOG_MODEL_H_
#include <memory>
#include "base/callback.h"
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/strings/string16.h"
#include "base/util/type_safety/pass_key.h"
#include "ui/base/models/dialog_model_field.h"
#include "ui/base/models/dialog_model_host.h"
#include "ui/base/ui_base_types.h"
namespace ui {
class ComboboxModel;
// Base class for a Delegate associated with (owned by) a model. Provides a link
// from the delegate back to the model it belongs to (through ::dialog_model()),
// from which fields and the DialogModelHost can be accessed.
class COMPONENT_EXPORT(UI_BASE) DialogModelDelegate {
public:
DialogModelDelegate() = default;
DialogModelDelegate(const DialogModelDelegate&) = delete;
DialogModelDelegate& operator=(const DialogModelDelegate&) = delete;
virtual ~DialogModelDelegate() = default;
DialogModel* dialog_model() { return dialog_model_; }
private:
friend class DialogModel;
void set_dialog_model(DialogModel* model) { dialog_model_ = model; }
DialogModel* dialog_model_ = nullptr;
};
// DialogModel represents a platform-and-toolkit agnostic data + behavior
// portion of a dialog. This contains the semantics of a dialog, whereas
// DialogModelHost implementations (like views::BubbleDialogModelHost) are
// responsible for interfacing with toolkits to display them. This provides a
// separation of concerns where a DialogModel only needs to be concerned with
// what goes into a dialog, not how it shows.
//
// Example usage (with views as an example DialogModelHost implementation). Note
// that visual presentation (except order of elements) is entirely up to
// DialogModelHost, and separate from client code:
//
// constexpr int kNameTextfield = 1;
// class Delegate : public ui::DialogModelDelegate {
// public:
// void OnDialogAccepted() {
// LOG(ERROR) << "Hello "
// << dialog_model()->GetTextfield(kNameTextfield)->text();
// }
// };
// auto model_delegate = std::make_unique<Delegate>();
// auto* model_delegate_ptr = model_delegate.get();
//
// auto dialog_model =
// ui::DialogModel::Builder(std::move(model_delegate))
// .SetTitle(base::ASCIIToUTF16("Hello, world!"))
// .AddDialogButton(ui::DIALOG_BUTTON_OK,
// l10n_util::GetStringUTF16(IDS_OK))
// .AddTextfield(
// base::ASCIIToUTF16("Name"), base::string16(),
// ui::DialogModelTextfield::Params().SetUniqueId(kNameTextfield))
// .SetAcceptCallback(
// base::BindOnce(&Delegate::OnDialogAccepted,
// base::Unretained(model_delegate_ptr)))
// .Build();
//
// // DialogModelBase::Host specific. In this example, uses views-specific
// // code to set a view as an anchor.
// auto bubble =
// std::make_unique<views::BubbleDialogModelHost>(std::move(dialog_model));
// bubble->SetAnchorView(anchor_view);
// views::Widget* const widget =
// views::BubbleDialogDelegateView::CreateBubble(bubble.release());
// widget->Show();
class COMPONENT_EXPORT(UI_BASE) DialogModel final {
public:
// Builder for DialogModel. Used for properties that are either only or
// commonly const after construction.
class COMPONENT_EXPORT(UI_BASE) Builder {
public:
explicit Builder(std::unique_ptr<DialogModelDelegate> delegate);
~Builder();
std::unique_ptr<DialogModel> Build() WARN_UNUSED_RESULT;
Builder& SetShowCloseButton(bool show_close_button);
Builder& SetTitle(base::string16 title);
// Called when the dialog is accepted, before it closes.
Builder& SetAcceptCallback(base::OnceClosure callback);
// Called when the dialog is cancelled.
Builder& SetCancelCallback(base::OnceClosure callback);
// Called when the dialog is explicitly closed (for instance, close-x). Not
// called during accept/cancel.
Builder& SetCloseCallback(base::OnceClosure callback);
// TODO(pbos): Clarify and enforce (through tests) that this is called after
// {accept,cancel,close} callbacks.
// Unconditionally called when the dialog closes. Called on top of
// {accept,cancel,close} callbacks.
Builder& SetWindowClosingCallback(base::OnceClosure callback);
// Adds a dialog button (ok, cancel) to the dialog. Note that the callbacks
// for these button actions should be set using SetAcceptCallback() and
// SetCancelCallback().
Builder& AddDialogButton(
DialogButton button,
base::string16 label,
const DialogModelButton::Params& params = DialogModelButton::Params());
// Use of the extra button in new dialogs are discouraged. If this is deemed
// necessary please double-check with UX before adding any new dialogs with
// them.
Builder& AddDialogExtraButton(base::string16 label,
const DialogModelButton::Params& params);
// Adds a textfield. See DialogModel::AddTextfield().
Builder& AddTextfield(base::string16 label,
base::string16 text,
const ui::DialogModelTextfield::Params& params);
// Adds a combobox. See DialogModel::AddCombobox().
Builder& AddCombobox(base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const DialogModelCombobox::Params& params);
// Sets which field should be initially focused in the dialog model. Must be
// called after that field has been added. Can only be called once.
Builder& SetInitiallyFocusedField(int unique_id);
private:
std::unique_ptr<DialogModel> model_;
};
DialogModel(util::PassKey<DialogModel::Builder>,
std::unique_ptr<DialogModelDelegate> delegate);
~DialogModel();
// The host in which this model is hosted. Set by the Host implementation
// during Host construction where it takes ownership of |this|.
DialogModelHost* host() { return host_; }
// Adds a labeled textfield (label: [text]) at the end of the dialog model.
void AddTextfield(base::string16 label,
base::string16 text,
const ui::DialogModelTextfield::Params& params);
// Adds a labeled combobox (label: [model]) at the end of the dialog model.
void AddCombobox(base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const DialogModelCombobox::Params& params);
// Gets DialogModelFields from their unique identifier. |unique_id| is
// supplied to AddX methods.
DialogModelField* GetFieldByUniqueId(int unique_id);
DialogModelButton* GetButtonByUniqueId(int unique_id);
DialogModelCombobox* GetComboboxByUniqueId(int unique_id);
DialogModelTextfield* GetTextfieldByUniqueId(int unique_id);
// Get dialog buttons.
DialogModelButton* GetDialogButton(DialogButton button);
DialogModelButton* GetExtraButton();
// Methods with util::PassKey<DialogModelHost> are for host implementations
// only.
void OnButtonPressed(util::PassKey<DialogModelHost>,
int id,
const Event& event);
void OnDialogAccepted(util::PassKey<DialogModelHost>);
void OnDialogCancelled(util::PassKey<DialogModelHost>);
void OnDialogClosed(util::PassKey<DialogModelHost>);
void OnComboboxPerformAction(util::PassKey<DialogModelHost>, int id);
void OnComboboxSelectedIndexChanged(util::PassKey<DialogModelHost>,
int id,
int index);
void OnTextfieldTextChanged(util::PassKey<DialogModelHost>,
int id,
base::string16 text);
void OnWindowClosing(util::PassKey<DialogModelHost>);
// Called when added to a DialogModelHost.
void set_host(util::PassKey<DialogModelHost>, DialogModelHost* host) {
host_ = host;
}
bool show_close_button(util::PassKey<DialogModelHost>) const {
return show_close_button_;
}
const base::string16& title(util::PassKey<DialogModelHost>) const {
return title_;
}
base::Optional<int> initially_focused_field(
util::PassKey<DialogModelHost>) const {
return initially_focused_field_;
}
// Accessor for ordered fields in the model. This includes DialogButtons even
// though they should be handled separately (OK button has fixed position in
// dialog).
const std::vector<std::unique_ptr<DialogModelField>>& fields(
util::PassKey<DialogModelHost>) {
return fields_;
}
private:
void AddDialogButton(int button,
base::string16 label,
const DialogModelButton::Params& params);
// TODO(pbos): See if the hosts can just return back the field pointer instead
// so we don't need to do lookup.
DialogModelField* GetFieldFromModelFieldId(int field_id);
DialogModelButton* GetButtonFromModelFieldId(int field_id);
DialogModelCombobox* GetComboboxFromModelFieldId(int field_id);
DialogModelTextfield* GetTextfieldFromModelFieldId(int field_id);
DialogModelField::Reservation ReserveField();
std::unique_ptr<DialogModelDelegate> delegate_;
DialogModelHost* host_ = nullptr;
bool show_close_button_ = false;
base::string16 title_;
static constexpr int kExtraButtonId = DIALOG_BUTTON_LAST + 1;
// kExtraButtonId is the last reserved id (ui::DialogButton are also reserved
// IDs).
int next_field_id_ = kExtraButtonId + 1;
std::vector<std::unique_ptr<DialogModelField>> fields_;
base::Optional<int> initially_focused_field_;
base::OnceClosure accept_callback_;
base::OnceClosure cancel_callback_;
base::OnceClosure close_callback_;
base::OnceClosure window_closing_callback_;
};
} // namespace ui
#endif // UI_BASE_MODELS_DIALOG_MODEL_H_
\ No newline at end of file
// Copyright 2020 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/base/models/dialog_model_field.h"
#include "ui/base/models/dialog_model.h"
namespace ui {
DialogModelField::DialogModelField(
const DialogModelField::Reservation& reservation,
DialogModelField::Type type,
int unique_id,
base::flat_set<Accelerator> accelerators)
: model_(reservation.model),
model_field_id_(reservation.model_field_id),
type_(type),
unique_id_(unique_id),
accelerators_(std::move(accelerators)) {
// TODO(pbos): Assert that unique_id_ is unique.
}
DialogModelField::~DialogModelField() = default;
DialogModelField::Reservation::Reservation(DialogModel* model,
int model_field_id)
: model(model), model_field_id(model_field_id) {}
DialogModelButton::Params::Params() = default;
DialogModelButton::Params::~Params() = default;
DialogModelButton::Params& DialogModelButton::Params::AddAccelerator(
Accelerator accelerator) {
accelerators_.insert(std::move(accelerator));
return *this;
}
DialogModelButton::Params& DialogModelButton::Params::SetCallback(
base::RepeatingCallback<void(const Event&)> callback) {
callback_ = std::move(callback);
return *this;
}
DialogModelButton::DialogModelButton(
const DialogModelField::Reservation& reservation,
base::string16 label,
const DialogModelButton::Params& params)
: DialogModelField(reservation,
kButton,
params.unique_id_,
params.accelerators_),
label_(std::move(label)),
callback_(params.callback_) {}
DialogModelButton::~DialogModelButton() = default;
DialogModelCombobox::Params::Params() = default;
DialogModelCombobox::Params::~Params() = default;
DialogModelCombobox::Params& DialogModelCombobox::Params::SetUniqueId(
int unique_id) {
DCHECK_GE(unique_id, 0);
unique_id_ = unique_id;
return *this;
}
DialogModelCombobox::Params& DialogModelCombobox::Params::SetCallback(
base::RepeatingClosure callback) {
callback_ = std::move(callback);
return *this;
}
DialogModelCombobox::Params& DialogModelCombobox::Params::AddAccelerator(
Accelerator accelerator) {
accelerators_.insert(std::move(accelerator));
return *this;
}
DialogModelCombobox::Params& DialogModelCombobox::Params::SetAccessibleName(
base::string16 accessible_name) {
accessible_name_ = std::move(accessible_name);
return *this;
}
DialogModelCombobox::DialogModelCombobox(
const DialogModelField::Reservation& reservation,
base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const DialogModelCombobox::Params& params)
: DialogModelField(reservation,
kCombobox,
params.unique_id_,
params.accelerators_),
label_(std::move(label)),
accessible_name_(params.accessible_name_),
selected_index_(combobox_model->GetDefaultIndex()),
combobox_model_(std::move(combobox_model)),
callback_(params.callback_) {}
DialogModelCombobox::~DialogModelCombobox() = default;
DialogModelTextfield::Params::Params() = default;
DialogModelTextfield::Params::~Params() = default;
DialogModelTextfield::Params& DialogModelTextfield::Params::SetUniqueId(
int unique_id) {
DCHECK_GE(unique_id, 0);
unique_id_ = unique_id;
return *this;
}
DialogModelTextfield::Params& DialogModelTextfield::Params::AddAccelerator(
Accelerator accelerator) {
accelerators_.insert(std::move(accelerator));
return *this;
}
DialogModelTextfield::Params& DialogModelTextfield::Params::SetAccessibleName(
base::string16 accessible_name) {
accessible_name_ = accessible_name;
return *this;
}
DialogModelTextfield::DialogModelTextfield(
const DialogModelField::Reservation& reservation,
base::string16 label,
base::string16 text,
const ui::DialogModelTextfield::Params& params)
: DialogModelField(reservation,
kTextfield,
params.unique_id_,
params.accelerators_),
label_(label),
accessible_name_(params.accessible_name_),
text_(std::move(text)) {}
DialogModelTextfield::~DialogModelTextfield() = default;
} // namespace ui
\ No newline at end of file
// Copyright 2020 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.
#ifndef UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
#define UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/strings/string16.h"
#include "base/util/type_safety/pass_key.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/models/combobox_model.h"
namespace ui {
class DialogModel;
class DialogModelHost;
class Event;
// These "field" classes represent entries in a DialogModel. They are owned
// by the model and either created through the model or DialogModel::Builder.
// These entries can be referred to by setting the field's unique id in
// construction parameters (::Params::SetUniqueId()). They can then later be
// acquired through DialogModel::GetFieldByUniqueId() methods.
// These fields own the data corresponding to their field. For instance, the
// text of a textfield in a model is read using DialogModelTextfield::text() and
// stays in sync with the visible dialog (through DialogModelHosts).
class COMPONENT_EXPORT(UI_BASE) DialogModelField {
public:
enum Type { kButton, kTextfield, kCombobox };
DialogModelField(const DialogModelField&) = delete;
DialogModelField& operator=(const DialogModelField&) = delete;
virtual ~DialogModelField();
// Accessors with util::PassKey<DialogModelHost> are only intended to be read
// by the DialogModelHost implementation.
const base::flat_set<Accelerator>& accelerators(
util::PassKey<DialogModelHost>) const {
return accelerators_;
}
// Reserved ID generated by the model. This ID is generated for every field
// and used to map from DialogModelHost fields (like views::Textfield) to
// DialogModelFields (like DialogModelTextfield).
// WARNING: This should not be confused with the ID set by subclasses'
// Params::SetUniqueId() methods.
// TODO(pbos): See if this int-to-field mapping can be replaced with using
// DialogModelField pointers in DialogModelHost. This is currently only used
// because views has a convenient views::View::SetID() function.
int model_field_id(util::PassKey<DialogModelHost>) const {
return model_field_id_;
}
Type type(util::PassKey<DialogModelHost>) const { return type_; }
protected:
// Struct that holds a reserved ID for the field in the model. This is
// protected to be able to be passed from DialogModelField children to the
// parent constructor. Its members are private because only DialogModel,
// DialogModelField and DialogModelHost should be able to read them.
// TODO(pbos): Reconsider whether this |model_field_id| can be avoided. This
// would take rewiring DialogModelHost to own a mapping between
// "DialogModelField*" and host classes (such as View) and not rely on things
// like View::SetID(model_field_id) to maintain that mapping. It would also
// require special handling of special buttons like DIALOG_BUTTON_OK.
struct Reservation {
private:
friend class DialogModel;
friend class DialogModelField;
// This is only to be constructed by DialogModel who makes the actual
// ID assignment.
Reservation(DialogModel* model, int model_field_id);
DialogModel* const model;
const int model_field_id;
};
DialogModelField(const Reservation& reservation,
Type type,
int unique_id,
base::flat_set<Accelerator> accelerators);
int model_field_id() const { return model_field_id_; }
private:
friend class DialogModel;
DialogModel* const model_;
const int model_field_id_;
const Type type_;
const int unique_id_;
const base::flat_set<Accelerator> accelerators_;
};
// Field class representing a dialog button.
class COMPONENT_EXPORT(UI_BASE) DialogModelButton : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& SetUniqueId(int unique_id);
// The button callback gets called when the button is activated. Whether
// that happens on key-press, release, etc. is implementation (and platform)
// dependent.
Params& SetCallback(base::RepeatingCallback<void(const Event&)> callback);
Params& AddAccelerator(Accelerator accelerator);
Params& SetAccessibleName(base::string16 accessible_name);
bool has_callback() const { return !!callback_; }
private:
friend class DialogModelButton;
int unique_id_ = -1;
base::RepeatingCallback<void(const Event&)> callback_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel (Reservation can't
// be created without one).
DialogModelButton(const Reservation& reservation,
base::string16 label,
const Params& params);
DialogModelButton(const DialogModelButton&) = delete;
DialogModelButton& operator=(const DialogModelButton&) = delete;
~DialogModelButton() override;
const base::string16& label() const { return label_; }
private:
friend class DialogModel;
const base::string16 label_;
base::RepeatingCallback<void(const Event&)> callback_;
};
// Field class representing a combobox and corresponding label to describe the
// combobox:
//
// <label> [combobox]
// Ex: Folder [My Bookmarks]
class COMPONENT_EXPORT(UI_BASE) DialogModelCombobox : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& SetUniqueId(int unique_id);
Params& AddAccelerator(Accelerator accelerator);
Params& SetAccessibleName(base::string16 accessible_name);
// The combobox callback is invoked when an item has been selected. This
// nominally happens when selecting an item in the combobox menu. The
// selection notably does not change by hovering different items in the
// combobox menu or navigating it with up/down keys as long as the menu is
// open.
Params& SetCallback(base::RepeatingClosure callback);
private:
friend class DialogModelCombobox;
const base::string16 label_;
int unique_id_ = -1;
base::string16 accessible_name_;
base::RepeatingClosure callback_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel (Reservation can't
// be created without one).
DialogModelCombobox(const Reservation& reservation,
base::string16 label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const Params& params);
DialogModelCombobox(const DialogModelCombobox&) = delete;
DialogModelCombobox& operator=(const DialogModelCombobox&) = delete;
~DialogModelCombobox() override;
const base::string16& label() const { return label_; }
const base::string16& accessible_name() const { return accessible_name_; }
int selected_index() const { return selected_index_; }
ui::ComboboxModel* combobox_model() { return combobox_model_.get(); }
private:
friend class DialogModel;
const base::string16 label_;
const base::string16 accessible_name_;
int selected_index_;
std::unique_ptr<ui::ComboboxModel> combobox_model_;
base::RepeatingClosure callback_;
};
// Field class representing a textfield and corresponding label to describe the
// textfield:
//
// <label> [textfield]
// Ex: Name [My email]
class COMPONENT_EXPORT(UI_BASE) DialogModelTextfield : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& SetUniqueId(int unique_id);
Params& AddAccelerator(Accelerator accelerator);
Params& SetAccessibleName(base::string16 accessible_name);
private:
friend class DialogModelTextfield;
int unique_id_ = -1;
base::string16 accessible_name_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel (Reservation can't
// be created without one).
DialogModelTextfield(const DialogModelField::Reservation& reservation,
base::string16 label,
base::string16 text,
const Params& params);
DialogModelTextfield(const DialogModelTextfield&) = delete;
DialogModelTextfield& operator=(const DialogModelTextfield&) = delete;
~DialogModelTextfield() override;
const base::string16& label() const { return label_; }
const base::string16& accessible_name() const { return accessible_name_; }
const base::string16& text() const { return text_; }
private:
friend class DialogModel;
const base::string16 label_;
const base::string16 accessible_name_;
base::string16 text_;
};
} // namespace ui
#endif // UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
\ No newline at end of file
// Copyright 2020 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.
#ifndef UI_BASE_MODELS_DIALOG_MODEL_HOST_H_
#define UI_BASE_MODELS_DIALOG_MODEL_HOST_H_
#include "base/util/type_safety/pass_key.h"
namespace ui {
class DialogModel;
// Platform-agnostic interface for toolkit integrations.
class COMPONENT_EXPORT(UI_BASE) DialogModelHost {
public:
// TODO(pbos): Try to get Close semantically synchronous (guarantee
// synchronous destruction of model), so it cannot be observed as
// asynchronous even if GetWidget()->Close() under Views is async.
virtual void Close() = 0;
// Selects all text of a textfield.
// TODO(pbos): Reconsider whether this should be implied by if the textfield
// is initially focused.
virtual void SelectAllText(int unique_id) = 0;
protected:
friend class DialogModel;
friend class DialogModelField;
// This PassKey is used to make sure that some methods on DialogModel
// are only called as part of the host integration.
util::PassKey<DialogModelHost> GetPassKey() {
return util::PassKey<DialogModelHost>();
}
// Called when various parts of the model changes.
// TODO(pbos): Break this down to API that says what was added/removed/changed
// to not have to reset everything.
virtual void OnModelChanged(DialogModel* model) = 0;
};
} // namespace ui
#endif // UI_BASE_MODELS_DIALOG_MODEL_HOST_H_
...@@ -100,6 +100,7 @@ component("views") { ...@@ -100,6 +100,7 @@ component("views") {
"border.h", "border.h",
"bubble/bubble_border.h", "bubble/bubble_border.h",
"bubble/bubble_dialog_delegate_view.h", "bubble/bubble_dialog_delegate_view.h",
"bubble/bubble_dialog_model_host.h",
"bubble/bubble_frame_view.h", "bubble/bubble_frame_view.h",
"bubble/info_bubble.h", "bubble/info_bubble.h",
"bubble/tooltip_icon.h", "bubble/tooltip_icon.h",
...@@ -312,6 +313,7 @@ component("views") { ...@@ -312,6 +313,7 @@ component("views") {
"border.cc", "border.cc",
"bubble/bubble_border.cc", "bubble/bubble_border.cc",
"bubble/bubble_dialog_delegate_view.cc", "bubble/bubble_dialog_delegate_view.cc",
"bubble/bubble_dialog_model_host.cc",
"bubble/bubble_frame_view.cc", "bubble/bubble_frame_view.cc",
"bubble/footnote_container_view.cc", "bubble/footnote_container_view.cc",
"bubble/info_bubble.cc", "bubble/info_bubble.cc",
......
// Copyright 2020 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/bubble/bubble_dialog_model_host.h"
#include <utility>
#include "ui/base/models/combobox_model.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/flex_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_provider.h"
namespace views {
namespace {
constexpr int kColumnId = 0;
DialogContentType FieldTypeToContentType(ui::DialogModelField::Type type) {
switch (type) {
case ui::DialogModelField::kButton:
return DialogContentType::CONTROL;
case ui::DialogModelField::kTextfield:
return DialogContentType::CONTROL;
case ui::DialogModelField::kCombobox:
return DialogContentType::CONTROL;
}
NOTREACHED();
return DialogContentType::CONTROL;
}
} // namespace
BubbleDialogModelHost::BubbleDialogModelHost(
std::unique_ptr<ui::DialogModel> model)
: model_(std::move(model)) {
model_->set_host(GetPassKey(), this);
ConfigureGridLayout();
SetAcceptCallback(base::BindOnce(&ui::DialogModel::OnDialogAccepted,
base::Unretained(model_.get()),
GetPassKey()));
SetCancelCallback(base::BindOnce(&ui::DialogModel::OnDialogCancelled,
base::Unretained(model_.get()),
GetPassKey()));
SetCloseCallback(base::BindOnce(&ui::DialogModel::OnDialogClosed,
base::Unretained(model_.get()),
GetPassKey()));
RegisterWindowClosingCallback(
base::BindOnce(&ui::DialogModel::OnWindowClosing,
base::Unretained(model_.get()), GetPassKey()));
int button_mask = ui::DIALOG_BUTTON_NONE;
// TODO(pbos): Separate dialog buttons from fields. This is not nice.
for (const auto& field : model_->fields(GetPassKey())) {
if (field->type(GetPassKey()) != ui::DialogModelField::kButton)
continue;
const auto* button = static_cast<const ui::DialogModelButton*>(field.get());
if (field->model_field_id(GetPassKey()) > ui::DIALOG_BUTTON_LAST) {
DCHECK_EQ(button, model_->GetExtraButton());
auto extra_button =
std::make_unique<views::MdTextButton>(this, button->label());
extra_button->SetID(field->model_field_id(GetPassKey()));
SetExtraView(std::move(extra_button));
continue;
}
button_mask |= field->model_field_id(GetPassKey());
SetButtons(button_mask);
SetButtonLabel(
static_cast<ui::DialogButton>(field->model_field_id(GetPassKey())),
button->label());
}
// Populate dialog using the observer functions to make sure they use the same
// code path as updates.
OnModelChanged(model_.get());
}
BubbleDialogModelHost::~BubbleDialogModelHost() {
// Remove children as they may refer to the soon-to-be-destructed model.
RemoveAllChildViews(true);
}
View* BubbleDialogModelHost::GetInitiallyFocusedView() {
if (model_->initially_focused_field(GetPassKey())) {
// TODO(pbos): Update this so that it works for dialog buttons.
View* focused_view = GetViewByID(
model_
->GetFieldByUniqueId(*model_->initially_focused_field(GetPassKey()))
->model_field_id(GetPassKey()));
// The dialog should be populated now so this should correspond to a view
// with this ID.
DCHECK(focused_view);
return focused_view;
}
return BubbleDialogDelegateView::GetInitiallyFocusedView();
}
void BubbleDialogModelHost::OnDialogInitialized() {
UpdateAccelerators();
}
void BubbleDialogModelHost::Close() {
// TODO(pbos): Synchronously destroy model here, as-if closing immediately.
DCHECK(GetWidget());
GetWidget()->Close();
}
void BubbleDialogModelHost::SelectAllText(int unique_id) {
static_cast<Textfield*>(
GetViewByID(model_->GetTextfieldByUniqueId(unique_id)->model_field_id(
GetPassKey())))
->SelectAll(false);
}
void BubbleDialogModelHost::OnModelChanged(ui::DialogModel* model) {
DCHECK(model == model_.get());
WidgetDelegate::SetTitle(model->title(GetPassKey()));
WidgetDelegate::SetShowCloseButton(model->show_close_button(GetPassKey()));
// TODO(pbos): When fixing the DCHECK below, keep views and update them. This
// is required to maintain view focus, for instance. Do not remove all
// children and recreate. Needs to dynamically insert/remove GridLayout rows.
DCHECK(children().empty()) << "TODO(pbos): Support changing the model after "
"host creation...";
bool first_row = true;
const auto& fields = model->fields(GetPassKey());
const DialogContentType first_field_content_type =
fields.empty()
? DialogContentType::CONTROL
: FieldTypeToContentType(fields.front()->type(GetPassKey()));
DialogContentType last_field_content_type = first_field_content_type;
for (const auto& field : fields) {
// TODO(pbos): This needs to take previous field type + next field type into
// account to do this properly.
if (!first_row) {
// TODO(pbos): Move DISTANCE_CONTROL_LIST_VERTICAL to
// views::LayoutProvider and replace "12" here.
GetGridLayout()->AddPaddingRow(GridLayout::kFixedSize, 12);
}
View* last_field = nullptr;
switch (field->type(GetPassKey())) {
case ui::DialogModelField::kButton:
// TODO(pbos): Add support for buttons that are part of content area.
continue;
case ui::DialogModelField::kTextfield:
last_field = AddOrUpdateTextfield(
static_cast<const ui::DialogModelTextfield&>(*field));
break;
case ui::DialogModelField::kCombobox:
last_field = AddOrUpdateCombobox(
static_cast<ui::DialogModelCombobox*>(field.get()));
break;
}
DCHECK(last_field);
last_field->SetID(field->model_field_id(GetPassKey()));
last_field_content_type = FieldTypeToContentType(field->type(GetPassKey()));
// TODO(pbos): Update logic here when mixing types.
first_row = false;
}
set_margins(LayoutProvider::Get()->GetDialogInsetsForContentType(
first_field_content_type, last_field_content_type));
UpdateAccelerators();
}
GridLayout* BubbleDialogModelHost::GetGridLayout() {
return static_cast<GridLayout*>(GetLayoutManager());
}
void BubbleDialogModelHost::ConfigureGridLayout() {
auto* grid_layout = SetLayoutManager(std::make_unique<GridLayout>());
LayoutProvider* const provider = LayoutProvider::Get();
const int between_padding =
provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_HORIZONTAL);
ColumnSet* const column_set = grid_layout->AddColumnSet(kColumnId);
column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER,
GridLayout::kFixedSize,
GridLayout::ColumnSize::kUsePreferred, 0, 0);
column_set->AddPaddingColumn(GridLayout::kFixedSize, between_padding);
column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1.0,
GridLayout::ColumnSize::kFixed, 0, 0);
}
Textfield* BubbleDialogModelHost::AddOrUpdateTextfield(
const ui::DialogModelTextfield& model) {
// TODO(pbos): Handle updating existing field.
DCHECK(!GetViewByID(model.model_field_id(GetPassKey())))
<< "BubbleDialogModelHost doesn't yet support updates to the model";
auto textfield = std::make_unique<Textfield>();
textfield->SetAccessibleName(
model.accessible_name().empty() ? model.text() : model.accessible_name());
textfield->SetText(model.text());
property_changed_subscriptions_.push_back(
textfield->AddTextChangedCallback(base::BindRepeating(
&BubbleDialogModelHost::NotifyTextfieldTextChanged,
base::Unretained(this), model.model_field_id(GetPassKey()),
textfield.get())));
auto* textfield_ptr = textfield.get();
AddLabelAndField(model.label(), std::move(textfield),
textfield_ptr->GetFontList());
return textfield_ptr;
}
Combobox* BubbleDialogModelHost::AddOrUpdateCombobox(
ui::DialogModelCombobox* model) {
// TODO(pbos): Handle updating existing field.
DCHECK(!GetViewByID(model->model_field_id(GetPassKey())))
<< "BubbleDialogModelHost doesn't yet support updates to the model";
auto combobox = std::make_unique<Combobox>(model->combobox_model());
combobox->SetAccessibleName(model->accessible_name().empty()
? model->label()
: model->accessible_name());
combobox->set_listener(this);
// TODO(pbos): Add subscription to combobox selected-index changes.
combobox->SetSelectedIndex(model->selected_index());
auto* combobox_ptr = combobox.get();
AddLabelAndField(model->label(), std::move(combobox),
combobox_ptr->GetFontList());
return combobox_ptr;
}
void BubbleDialogModelHost::AddLabelAndField(const base::string16& label_text,
std::unique_ptr<View> field,
const gfx::FontList& field_font) {
constexpr int kFontContext = style::CONTEXT_LABEL;
constexpr int kFontStyle = style::STYLE_PRIMARY;
int row_height = LayoutProvider::GetControlHeightForFont(
kFontContext, kFontStyle, field_font);
GridLayout* const layout = GetGridLayout();
layout->StartRow(GridLayout::kFixedSize, kColumnId, row_height);
layout->AddView(
std::make_unique<Label>(label_text, kFontContext, kFontStyle));
layout->AddView(std::move(field));
}
void BubbleDialogModelHost::NotifyTextfieldTextChanged(int id,
Textfield* textfield) {
model_->OnTextfieldTextChanged(GetPassKey(), id, textfield->GetText());
}
void BubbleDialogModelHost::NotifyComboboxSelectedIndexChanged(
int id,
Combobox* combobox) {
model_->OnComboboxSelectedIndexChanged(GetPassKey(), id,
combobox->GetSelectedIndex());
}
void BubbleDialogModelHost::ButtonPressed(Button* sender,
const ui::Event& event) {
model_->OnButtonPressed(GetPassKey(), sender->GetID(), event);
}
void BubbleDialogModelHost::OnPerformAction(Combobox* combobox) {
// TODO(pbos): This should be a subscription through the Combobox directly,
// but Combobox right now doesn't support listening to selected-index changes.
NotifyComboboxSelectedIndexChanged(combobox->GetID(), combobox);
model_->OnComboboxPerformAction(GetPassKey(), combobox->GetID());
}
void BubbleDialogModelHost::UpdateAccelerators() {
// Dialog buttons can't be accessed before the widget is created. Delay until
// ::OnDialogInitialized().
if (!GetWidget())
return;
for (const auto& field : model_->fields(GetPassKey())) {
if (field->accelerators(GetPassKey()).empty())
continue;
View* view = nullptr;
if (field->model_field_id(GetPassKey()) == ui::DIALOG_BUTTON_OK) {
view = GetOkButton();
} else if (field->model_field_id(GetPassKey()) ==
ui::DIALOG_BUTTON_CANCEL) {
view = GetCancelButton();
} else if (field.get() == model_->GetExtraButton()) {
view = GetExtraView();
} else {
view = GetViewByID(field->model_field_id(GetPassKey()));
}
DCHECK(view);
view->ResetAccelerators();
for (const auto& accelerator : field->accelerators(GetPassKey()))
view->AddAccelerator(accelerator);
}
}
} // namespace views
// Copyright 2020 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.
#ifndef UI_VIEWS_BUBBLE_BUBBLE_DIALOG_MODEL_HOST_H_
#define UI_VIEWS_BUBBLE_BUBBLE_DIALOG_MODEL_HOST_H_
#include <memory>
#include <vector>
#include "base/callback.h"
#include "ui/base/models/dialog_model.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/combobox/combobox_listener.h"
namespace views {
class Combobox;
class GridLayout;
class Textfield;
// BubbleDialogModelHost is a views implementation of ui::DialogModelHost which
// hosts a ui::DialogModels as a BubbleDialogDelegateView. This exposes
// views-specific methods such as SetAnchorView(), SetArrow() and
// SetHighlightedButton(). For methods that are reflected in ui::DialogModelHost
// (such as ::Close()), preferusing the ui::DialogModelHost to avoid
// platform-specific code (GetWidget()->Close()) where unnecessary. For those
// methods, note that this can be retrieved as a ui::DialogModelHost through
// DialogModel::host(). This helps minimize platform-specific code from
// platform-agnostic model-delegate code.
class VIEWS_EXPORT BubbleDialogModelHost : public BubbleDialogDelegateView,
public ui::DialogModelHost,
public ButtonListener,
public ComboboxListener {
public:
// Constructs a BubbleDialogModelHost, which for most purposes is to used as a
// BubbleDialogDelegateView. The BubbleDialogDelegateView is nominally handed
// to BubbleDialogDelegateView::CreateBubble() which returns a Widget that has
// taken ownership of the bubble. Widget::Show() finally shows the bubble.
explicit BubbleDialogModelHost(std::unique_ptr<ui::DialogModel> model);
~BubbleDialogModelHost() override;
// BubbleDialogDelegateView:
// TODO(pbos): Populate initparams with initial view instead of overriding
// GetInitiallyFocusedView().
View* GetInitiallyFocusedView() override;
void OnDialogInitialized() override;
// ui::DialogModelHost:
void Close() override;
void SelectAllText(int unique_id) override;
void OnModelChanged(ui::DialogModel* model) override;
// ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// ComboboxListener:
void OnPerformAction(views::Combobox* combobox) override;
private:
GridLayout* GetGridLayout();
void ConfigureGridLayout();
Textfield* AddOrUpdateTextfield(const ui::DialogModelTextfield& field);
Combobox* AddOrUpdateCombobox(ui::DialogModelCombobox* field);
void AddLabelAndField(const base::string16& label_text,
std::unique_ptr<views::View> field,
const gfx::FontList& field_font);
void UpdateAccelerators();
void NotifyTextfieldTextChanged(int id, views::Textfield* textfield);
void NotifyComboboxSelectedIndexChanged(int id, views::Combobox* combobox);
std::unique_ptr<ui::DialogModel> model_;
std::vector<PropertyChangedSubscription> property_changed_subscriptions_;
};
} // namespace views
#endif // UI_VIEWS_BUBBLE_BUBBLE_DIALOG_MODEL_HOST_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment