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