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