OLD | NEW |
| (Empty) |
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 "chrome/browser/ui/views/website_settings/permission_selector_row.h" | |
6 | |
7 #include "base/i18n/rtl.h" | |
8 #include "base/macros.h" | |
9 #include "base/strings/utf_string_conversions.h" | |
10 #include "chrome/browser/ui/page_info/permission_menu_model.h" | |
11 #include "chrome/browser/ui/page_info/website_settings_ui.h" | |
12 #include "chrome/browser/ui/views/website_settings/non_accessible_image_view.h" | |
13 #include "chrome/browser/ui/views/website_settings/website_settings_popup_view.h
" | |
14 #include "chrome/grit/generated_resources.h" | |
15 #include "ui/accessibility/ax_node_data.h" | |
16 #include "ui/base/material_design/material_design_controller.h" | |
17 #include "ui/base/models/combobox_model.h" | |
18 #include "ui/gfx/image/image.h" | |
19 #include "ui/views/controls/button/menu_button.h" | |
20 #include "ui/views/controls/combobox/combobox.h" | |
21 #include "ui/views/controls/combobox/combobox_listener.h" | |
22 #include "ui/views/controls/image_view.h" | |
23 #include "ui/views/controls/label.h" | |
24 #include "ui/views/controls/menu/menu_runner.h" | |
25 #include "ui/views/layout/grid_layout.h" | |
26 #include "ui/views/view.h" | |
27 #include "ui/views/widget/widget.h" | |
28 | |
29 namespace internal { | |
30 | |
31 // The |PermissionMenuButton| provides a menu for selecting a setting a | |
32 // permissions type. | |
33 class PermissionMenuButton : public views::MenuButton, | |
34 public views::MenuButtonListener { | |
35 public: | |
36 // Creates a new |PermissionMenuButton| with the passed |text|. The ownership | |
37 // of the |model| remains with the caller and is not transfered to the | |
38 // |PermissionMenuButton|. If the |show_menu_marker| flag is true, then a | |
39 // small icon is be displayed next to the button |text|, indicating that the | |
40 // button opens a drop down menu. | |
41 PermissionMenuButton(const base::string16& text, | |
42 PermissionMenuModel* model, | |
43 bool show_menu_marker); | |
44 ~PermissionMenuButton() override; | |
45 | |
46 // Overridden from views::View. | |
47 void GetAccessibleNodeData(ui::AXNodeData* node_data) override; | |
48 void OnNativeThemeChanged(const ui::NativeTheme* theme) override; | |
49 | |
50 private: | |
51 // Overridden from views::MenuButtonListener. | |
52 void OnMenuButtonClicked(views::MenuButton* source, | |
53 const gfx::Point& point, | |
54 const ui::Event* event) override; | |
55 | |
56 PermissionMenuModel* menu_model_; // Owned by |PermissionSelectorRow|. | |
57 std::unique_ptr<views::MenuRunner> menu_runner_; | |
58 | |
59 bool is_rtl_display_; | |
60 | |
61 DISALLOW_COPY_AND_ASSIGN(PermissionMenuButton); | |
62 }; | |
63 | |
64 /////////////////////////////////////////////////////////////////////////////// | |
65 // PermissionMenuButton | |
66 /////////////////////////////////////////////////////////////////////////////// | |
67 | |
68 PermissionMenuButton::PermissionMenuButton(const base::string16& text, | |
69 PermissionMenuModel* model, | |
70 bool show_menu_marker) | |
71 : MenuButton(text, this, show_menu_marker), menu_model_(model) { | |
72 // Since PermissionMenuButtons are added to a GridLayout, they are not always | |
73 // sized to their preferred size. Disclosure arrows are always right-aligned, | |
74 // so if the text is not right-aligned, awkward space appears between the text | |
75 // and the arrow. | |
76 SetHorizontalAlignment(gfx::ALIGN_RIGHT); | |
77 | |
78 // Update the themed border before the NativeTheme is applied. Usually this | |
79 // happens in a call to LabelButton::OnNativeThemeChanged(). However, if | |
80 // PermissionMenuButton called that from its override, the NativeTheme would | |
81 // be available, and the button would get native GTK styling on Linux. | |
82 UpdateThemedBorder(); | |
83 | |
84 SetFocusForPlatform(); | |
85 set_request_focus_on_press(true); | |
86 is_rtl_display_ = | |
87 base::i18n::RIGHT_TO_LEFT == base::i18n::GetStringDirection(text); | |
88 } | |
89 | |
90 PermissionMenuButton::~PermissionMenuButton() { | |
91 } | |
92 | |
93 void PermissionMenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) { | |
94 MenuButton::GetAccessibleNodeData(node_data); | |
95 node_data->SetValue(GetText()); | |
96 } | |
97 | |
98 void PermissionMenuButton::OnNativeThemeChanged(const ui::NativeTheme* theme) { | |
99 SetTextColor(views::Button::STATE_NORMAL, theme->GetSystemColor( | |
100 ui::NativeTheme::kColorId_LabelEnabledColor)); | |
101 SetTextColor(views::Button::STATE_HOVERED, theme->GetSystemColor( | |
102 ui::NativeTheme::kColorId_LabelEnabledColor)); | |
103 SetTextColor(views::Button::STATE_DISABLED, theme->GetSystemColor( | |
104 ui::NativeTheme::kColorId_LabelDisabledColor)); | |
105 } | |
106 | |
107 void PermissionMenuButton::OnMenuButtonClicked(views::MenuButton* source, | |
108 const gfx::Point& point, | |
109 const ui::Event* event) { | |
110 menu_runner_.reset(new views::MenuRunner( | |
111 menu_model_, | |
112 views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC)); | |
113 | |
114 gfx::Point p(point); | |
115 p.Offset(is_rtl_display_ ? source->width() : -source->width(), 0); | |
116 menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), this, | |
117 gfx::Rect(p, gfx::Size()), views::MENU_ANCHOR_TOPLEFT, | |
118 ui::MENU_SOURCE_NONE); | |
119 } | |
120 | |
121 // This class adapts a |PermissionMenuModel| into a |ui::ComboboxModel| so that | |
122 // |PermissionCombobox| can use it. | |
123 class ComboboxModelAdapter : public ui::ComboboxModel { | |
124 public: | |
125 explicit ComboboxModelAdapter(PermissionMenuModel* model) : model_(model) {} | |
126 ~ComboboxModelAdapter() override {} | |
127 | |
128 void OnPerformAction(int index); | |
129 | |
130 // Returns the checked index of the underlying PermissionMenuModel, of which | |
131 // there must be exactly one. This is used to choose which index is selected | |
132 // in the PermissionCombobox. | |
133 int GetCheckedIndex(); | |
134 | |
135 // ui::ComboboxModel: | |
136 int GetItemCount() const override; | |
137 base::string16 GetItemAt(int index) override; | |
138 | |
139 private: | |
140 PermissionMenuModel* model_; | |
141 }; | |
142 | |
143 void ComboboxModelAdapter::OnPerformAction(int index) { | |
144 model_->ExecuteCommand(index, 0); | |
145 } | |
146 | |
147 int ComboboxModelAdapter::GetCheckedIndex() { | |
148 int checked_index = -1; | |
149 for (int i = 0; i < model_->GetItemCount(); ++i) { | |
150 if (model_->IsCommandIdChecked(i)) { | |
151 // This function keeps track of |checked_index| instead of returning early | |
152 // here so that it can DCHECK that there's exactly one selected item, | |
153 // which is not normally guaranteed by MenuModel, but *is* true of | |
154 // PermissionMenuModel. | |
155 DCHECK_EQ(checked_index, -1); | |
156 checked_index = i; | |
157 } | |
158 } | |
159 return checked_index; | |
160 } | |
161 | |
162 int ComboboxModelAdapter::GetItemCount() const { | |
163 DCHECK(model_); | |
164 return model_->GetItemCount(); | |
165 } | |
166 | |
167 base::string16 ComboboxModelAdapter::GetItemAt(int index) { | |
168 return model_->GetLabelAt(index); | |
169 } | |
170 | |
171 // The |PermissionCombobox| provides a combobox for selecting a permission type. | |
172 // This is only used on platforms where the permission dialog uses a combobox | |
173 // instead of a MenuButton (currently, Mac). | |
174 class PermissionCombobox : public views::Combobox, | |
175 public views::ComboboxListener { | |
176 public: | |
177 PermissionCombobox(ComboboxModelAdapter* model, | |
178 bool enabled, | |
179 bool use_default); | |
180 ~PermissionCombobox() override; | |
181 | |
182 void UpdateSelectedIndex(bool use_default); | |
183 | |
184 private: | |
185 // views::Combobox: | |
186 void OnPaintBorder(gfx::Canvas* canvas) override; | |
187 | |
188 // views::ComboboxListener: | |
189 void OnPerformAction(Combobox* combobox) override; | |
190 | |
191 ComboboxModelAdapter* model_; | |
192 }; | |
193 | |
194 PermissionCombobox::PermissionCombobox(ComboboxModelAdapter* model, | |
195 bool enabled, | |
196 bool use_default) | |
197 : views::Combobox(model), model_(model) { | |
198 set_listener(this); | |
199 SetEnabled(enabled); | |
200 UpdateSelectedIndex(use_default); | |
201 if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { | |
202 set_size_to_largest_label(false); | |
203 ModelChanged(); | |
204 } | |
205 } | |
206 | |
207 PermissionCombobox::~PermissionCombobox() {} | |
208 | |
209 void PermissionCombobox::UpdateSelectedIndex(bool use_default) { | |
210 int index = model_->GetCheckedIndex(); | |
211 if (use_default && index == -1) | |
212 index = 0; | |
213 SetSelectedIndex(index); | |
214 } | |
215 | |
216 void PermissionCombobox::OnPaintBorder(gfx::Canvas* canvas) { | |
217 // No border except a focus indicator for MD mode. | |
218 if (ui::MaterialDesignController::IsSecondaryUiMaterial() && !HasFocus()) | |
219 return; | |
220 Combobox::OnPaintBorder(canvas); | |
221 } | |
222 | |
223 void PermissionCombobox::OnPerformAction(Combobox* combobox) { | |
224 model_->OnPerformAction(combobox->selected_index()); | |
225 } | |
226 | |
227 } // namespace internal | |
228 | |
229 /////////////////////////////////////////////////////////////////////////////// | |
230 // PermissionSelectorRow | |
231 /////////////////////////////////////////////////////////////////////////////// | |
232 | |
233 PermissionSelectorRow::PermissionSelectorRow( | |
234 Profile* profile, | |
235 const GURL& url, | |
236 const WebsiteSettingsUI::PermissionInfo& permission, | |
237 views::GridLayout* layout) | |
238 : profile_(profile), icon_(NULL), menu_button_(NULL), combobox_(NULL) { | |
239 // Create the permission icon. | |
240 icon_ = new NonAccessibleImageView(); | |
241 const gfx::Image& image = WebsiteSettingsUI::GetPermissionIcon(permission); | |
242 icon_->SetImage(image.ToImageSkia()); | |
243 layout->AddView(icon_, 1, 1, views::GridLayout::CENTER, | |
244 views::GridLayout::CENTER); | |
245 // Create the label that displays the permission type. | |
246 label_ = new views::Label( | |
247 WebsiteSettingsUI::PermissionTypeToUIString(permission.type)); | |
248 layout->AddView(label_, 1, 1, views::GridLayout::LEADING, | |
249 views::GridLayout::CENTER); | |
250 // Create the menu model. | |
251 menu_model_.reset(new PermissionMenuModel( | |
252 profile, url, permission, | |
253 base::Bind(&PermissionSelectorRow::PermissionChanged, | |
254 base::Unretained(this)))); | |
255 | |
256 // Create the permission menu button. | |
257 #if defined(OS_MACOSX) | |
258 bool use_real_combobox = true; | |
259 #else | |
260 bool use_real_combobox = | |
261 ui::MaterialDesignController::IsSecondaryUiMaterial(); | |
262 #endif | |
263 if (use_real_combobox) | |
264 InitializeComboboxView(layout, permission); | |
265 else | |
266 InitializeMenuButtonView(layout, permission); | |
267 } | |
268 | |
269 void PermissionSelectorRow::AddObserver( | |
270 PermissionSelectorRowObserver* observer) { | |
271 observer_list_.AddObserver(observer); | |
272 } | |
273 | |
274 PermissionSelectorRow::~PermissionSelectorRow() { | |
275 // Gross. On paper the Combobox and the ComboboxModelAdapter are both owned by | |
276 // this class, but actually, the Combobox is owned by View and will be | |
277 // destroyed in ~View(), which runs *after* ~PermissionSelectorRow() is done, | |
278 // which means the Combobox gets destroyed after its ComboboxModel, which | |
279 // causes an explosion when the Combobox attempts to stop observing the | |
280 // ComboboxModel. This hack ensures the Combobox is deleted before its | |
281 // ComboboxModel. | |
282 // | |
283 // Technically, the MenuButton has the same problem, but MenuButton doesn't | |
284 // use its model in its destructor. | |
285 if (combobox_) | |
286 combobox_->parent()->RemoveChildView(combobox_); | |
287 } | |
288 | |
289 void PermissionSelectorRow::InitializeMenuButtonView( | |
290 views::GridLayout* layout, | |
291 const WebsiteSettingsUI::PermissionInfo& permission) { | |
292 bool button_enabled = | |
293 permission.source == content_settings::SETTING_SOURCE_USER; | |
294 menu_button_ = new internal::PermissionMenuButton( | |
295 WebsiteSettingsUI::PermissionActionToUIString( | |
296 profile_, permission.type, permission.setting, | |
297 permission.default_setting, permission.source), | |
298 menu_model_.get(), button_enabled); | |
299 menu_button_->SetEnabled(button_enabled); | |
300 menu_button_->SetAccessibleName( | |
301 WebsiteSettingsUI::PermissionTypeToUIString(permission.type)); | |
302 layout->AddView(menu_button_); | |
303 } | |
304 | |
305 void PermissionSelectorRow::InitializeComboboxView( | |
306 views::GridLayout* layout, | |
307 const WebsiteSettingsUI::PermissionInfo& permission) { | |
308 bool button_enabled = | |
309 permission.source == content_settings::SETTING_SOURCE_USER; | |
310 combobox_model_adapter_.reset( | |
311 new internal::ComboboxModelAdapter(menu_model_.get())); | |
312 combobox_ = new internal::PermissionCombobox( | |
313 combobox_model_adapter_.get(), button_enabled, true); | |
314 combobox_->SetEnabled(button_enabled); | |
315 combobox_->SetAccessibleName( | |
316 WebsiteSettingsUI::PermissionTypeToUIString(permission.type)); | |
317 layout->AddView(combobox_); | |
318 } | |
319 | |
320 void PermissionSelectorRow::PermissionChanged( | |
321 const WebsiteSettingsUI::PermissionInfo& permission) { | |
322 // Change the permission icon to reflect the selected setting. | |
323 const gfx::Image& image = WebsiteSettingsUI::GetPermissionIcon(permission); | |
324 icon_->SetImage(image.ToImageSkia()); | |
325 | |
326 // Update the menu button text to reflect the new setting. | |
327 if (menu_button_) { | |
328 menu_button_->SetText(WebsiteSettingsUI::PermissionActionToUIString( | |
329 profile_, permission.type, permission.setting, | |
330 permission.default_setting, content_settings::SETTING_SOURCE_USER)); | |
331 menu_button_->SizeToPreferredSize(); | |
332 // Re-layout will be done at the |WebsiteSettingsPopupView| level, since | |
333 // that view may need to resize itself to accomodate the new sizes of its | |
334 // contents. | |
335 menu_button_->InvalidateLayout(); | |
336 } else if (combobox_) { | |
337 bool use_default = permission.setting == CONTENT_SETTING_DEFAULT; | |
338 combobox_->UpdateSelectedIndex(use_default); | |
339 } | |
340 | |
341 for (PermissionSelectorRowObserver& observer : observer_list_) | |
342 observer.OnPermissionChanged(permission); | |
343 } | |
344 | |
345 views::View* PermissionSelectorRow::button() { | |
346 // These casts are required because the two arms of a ?: cannot have different | |
347 // types T1 and T2, even if the resulting value of the ?: is about to be a T | |
348 // and T1 and T2 are both subtypes of T. | |
349 return menu_button_ ? static_cast<views::View*>(menu_button_) | |
350 : static_cast<views::View*>(combobox_); | |
351 } | |
OLD | NEW |