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.
5 #import "chrome/browser/ui/cocoa/content_settings/content_setting_bubble_cocoa.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/stl_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/content_settings/host_content_settings_map.h"
13 #include "chrome/browser/plugins/plugin_finder.h"
14 #include "chrome/browser/plugins/plugin_metadata.h"
15 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
16 #import "chrome/browser/ui/cocoa/l10n_util.h"
17 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
18 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
19 #include "content/public/browser/plugin_service.h"
20 #include "grit/generated_resources.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
24 #include "ui/base/l10n/l10n_util.h"
26 using content::PluginService;
30 // Height of one link in the popup list.
31 const int kLinkHeight = 16;
33 // Space between two popup links.
34 const int kLinkPadding = 4;
36 // Space taken in total by one popup link.
37 const int kLinkLineHeight = kLinkHeight + kLinkPadding;
39 // Space between popup list and surrounding UI elements.
40 const int kLinkOuterPadding = 8;
42 // Height of each of the labels in the geolocation bubble.
43 const int kGeoLabelHeight = 14;
45 // Height of the "Clear" button in the geolocation bubble.
46 const int kGeoClearButtonHeight = 17;
48 // General padding between elements in the geolocation bubble.
49 const int kGeoPadding = 8;
51 // Padding between host names in the geolocation bubble.
52 const int kGeoHostPadding = 4;
54 // Minimal padding between "Manage" and "Done" buttons.
55 const int kManageDonePadding = 8;
57 // Padding between radio buttons and media menus buttons in the media bubble.
58 const int kMediaMenuVerticalPadding = 25;
60 // Padding between media menu elements in the media bubble.
61 const int kMediaMenuElementVerticalPadding = 5;
63 // The amount of horizontal space between the media menu title and the border.
64 const int kMediaMenuTitleHorizontalPadding = 10;
66 // The minimum width of the media menu buttons.
67 const CGFloat kMinMediaMenuButtonWidth = 100;
69 // Height of each of the labels in the MIDI bubble.
70 const int kMIDISysExLabelHeight = 14;
72 // Height of the "Clear" button in the MIDI bubble.
73 const int kMIDISysExClearButtonHeight = 17;
75 // General padding between elements in the MIDI bubble.
76 const int kMIDISysExPadding = 8;
78 // Padding between host names in the MIDI bubble.
79 const int kMIDISysExHostPadding = 4;
81 void SetControlSize(NSControl* control, NSControlSize controlSize) {
82 CGFloat fontSize = [NSFont systemFontSizeForControlSize:controlSize];
83 NSCell* cell = [control cell];
84 NSFont* font = [NSFont fontWithName:[[cell font] fontName] size:fontSize];
86 [cell setControlSize:controlSize];
89 // Returns an autoreleased NSTextField that is configured to look like a Label
90 // looks in Interface Builder.
91 NSTextField* LabelWithFrame(NSString* text, const NSRect& frame) {
92 NSTextField* label = [[NSTextField alloc] initWithFrame:frame];
93 [label setStringValue:text];
94 [label setSelectable:NO];
95 [label setBezeled:NO];
96 return [label autorelease];
99 // Sets the title for the popup button.
100 void SetTitleForPopUpButton(NSPopUpButton* button, NSString* title) {
101 base::scoped_nsobject<NSMenuItem> titleItem([[NSMenuItem alloc] init]);
102 [titleItem setTitle:title];
103 [[button cell] setUsesItemFromMenu:NO];
104 [[button cell] setMenuItem:titleItem.get()];
107 // Builds the popup button menu from the menu model and returns the width of the
108 // longgest item as the width of the popup menu.
109 CGFloat BuildPopUpMenuFromModel(NSPopUpButton* button,
110 ContentSettingMediaMenuModel* model,
111 const std::string& title,
113 [[button cell] setControlSize:NSSmallControlSize];
114 [[button cell] setArrowPosition:NSPopUpArrowAtBottom];
115 [button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
116 [button setButtonType:NSMomentaryPushInButton];
117 [button setAlignment:NSLeftTextAlignment];
118 [button setAutoresizingMask:NSViewMinXMargin];
119 [button setAction:@selector(mediaMenuChanged:)];
122 CGFloat menuWidth = 0;
123 for (int i = 0; i < model->GetItemCount(); ++i) {
124 NSString* itemTitle =
125 base::SysUTF16ToNSString(model->GetLabelAt(i));
126 [button addItemWithTitle:itemTitle];
127 [[button lastItem] setTag:i];
129 if (UTF16ToUTF8(model->GetLabelAt(i)) == title)
130 [button selectItemWithTag:i];
132 // Determine the largest possible size for this button.
133 NSDictionary* textAttributes =
134 [NSDictionary dictionaryWithObject:[button font]
135 forKey:NSFontAttributeName];
136 NSSize size = [itemTitle sizeWithAttributes:textAttributes];
137 NSRect buttonFrame = [button frame];
138 NSRect titleRect = [[button cell] titleRectForBounds:buttonFrame];
139 CGFloat width = size.width + NSWidth(buttonFrame) - NSWidth(titleRect) +
140 kMediaMenuTitleHorizontalPadding;
141 menuWidth = std::max(menuWidth, width);
144 if (!model->GetItemCount()) {
145 // Show a "None available" title and grey out the menu when there is no
147 SetTitleForPopUpButton(
148 button, l10n_util::GetNSString(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
149 [button setEnabled:NO];
151 SetTitleForPopUpButton(button, base::SysUTF8ToNSString(title));
153 // Disable the device selection when the website is managing the devices
156 [button setEnabled:NO];
164 namespace content_setting_bubble {
166 MediaMenuParts::MediaMenuParts(content::MediaStreamType type,
170 MediaMenuParts::~MediaMenuParts() {}
172 } // namespace content_setting_bubble
174 @interface ContentSettingBubbleController(Private)
175 - (id)initWithModel:(ContentSettingBubbleModel*)settingsBubbleModel
176 parentWindow:(NSWindow*)parentWindow
177 anchoredAt:(NSPoint)anchoredAt;
178 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
179 title:(NSString*)title
181 referenceFrame:(NSRect)referenceFrame;
182 - (void)initializeBlockedPluginsList;
183 - (void)initializeTitle;
184 - (void)initializeRadioGroup;
185 - (void)initializePopupList;
186 - (void)initializeGeoLists;
187 - (void)initializeMediaMenus;
188 - (void)initializeMIDISysExLists;
189 - (void)sizeToFitLoadButton;
190 - (void)initManageDoneButtons;
191 - (void)removeInfoButton;
192 - (void)popupLinkClicked:(id)sender;
193 - (void)clearGeolocationForCurrentHost:(id)sender;
194 - (void)clearMIDISysExForCurrentHost:(id)sender;
197 @implementation ContentSettingBubbleController
199 + (ContentSettingBubbleController*)
200 showForModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
201 parentWindow:(NSWindow*)parentWindow
202 anchoredAt:(NSPoint)anchor {
203 // Autoreleases itself on bubble close.
204 return [[ContentSettingBubbleController alloc]
205 initWithModel:contentSettingBubbleModel
206 parentWindow:parentWindow
210 - (id)initWithModel:(ContentSettingBubbleModel*)contentSettingBubbleModel
211 parentWindow:(NSWindow*)parentWindow
212 anchoredAt:(NSPoint)anchoredAt {
213 // This method takes ownership of |contentSettingBubbleModel| in all cases.
214 scoped_ptr<ContentSettingBubbleModel> model(contentSettingBubbleModel);
217 ContentSettingsType settingsType = model->content_type();
218 NSString* nibPath = @"";
219 switch (settingsType) {
220 case CONTENT_SETTINGS_TYPE_COOKIES:
221 nibPath = @"ContentBlockedCookies"; break;
222 case CONTENT_SETTINGS_TYPE_IMAGES:
223 case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
224 case CONTENT_SETTINGS_TYPE_PPAPI_BROKER:
225 nibPath = @"ContentBlockedSimple"; break;
226 case CONTENT_SETTINGS_TYPE_PLUGINS:
227 nibPath = @"ContentBlockedPlugins"; break;
228 case CONTENT_SETTINGS_TYPE_POPUPS:
229 nibPath = @"ContentBlockedPopups"; break;
230 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
231 nibPath = @"ContentBlockedGeolocation"; break;
232 case CONTENT_SETTINGS_TYPE_MIXEDSCRIPT:
233 nibPath = @"ContentBlockedMixedScript"; break;
234 case CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS:
235 nibPath = @"ContentProtocolHandlers"; break;
236 case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
237 nibPath = @"ContentBlockedMedia"; break;
238 case CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS:
239 nibPath = @"ContentBlockedDownloads"; break;
240 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
241 nibPath = @"ContentBlockedMIDISysEx"; break;
242 // These content types have no bubble:
243 case CONTENT_SETTINGS_TYPE_DEFAULT:
244 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
245 case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
246 case CONTENT_SETTINGS_TYPE_FULLSCREEN:
247 case CONTENT_SETTINGS_TYPE_MOUSELOCK:
248 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
249 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
250 case CONTENT_SETTINGS_NUM_TYPES:
253 if ((self = [super initWithWindowNibPath:nibPath
254 parentWindow:parentWindow
255 anchoredAt:anchoredAt])) {
256 contentSettingBubbleModel_.reset(model.release());
257 [self showWindow:nil];
263 STLDeleteValues(&mediaMenus_);
267 - (void)initializeTitle {
271 NSString* label = base::SysUTF8ToNSString(
272 contentSettingBubbleModel_->bubble_content().title);
273 [titleLabel_ setStringValue:label];
275 // Layout title post-localization.
276 CGFloat deltaY = [GTMUILocalizerAndLayoutTweaker
277 sizeToFitFixedWidthTextField:titleLabel_];
278 NSRect windowFrame = [[self window] frame];
279 windowFrame.size.height += deltaY;
280 [[self window] setFrame:windowFrame display:NO];
281 NSRect titleFrame = [titleLabel_ frame];
282 titleFrame.origin.y -= deltaY;
283 [titleLabel_ setFrame:titleFrame];
286 - (void)initializeRadioGroup {
287 // NOTE! Tags in the xib files must match the order of the radio buttons
288 // passed in the radio_group and be 1-based, not 0-based.
289 const ContentSettingBubbleModel::RadioGroup& radio_group =
290 contentSettingBubbleModel_->bubble_content().radio_group;
292 // Select appropriate radio button.
293 [allowBlockRadioGroup_ selectCellWithTag: radio_group.default_item + 1];
295 const ContentSettingBubbleModel::RadioItems& radio_items =
296 radio_group.radio_items;
297 for (size_t ii = 0; ii < radio_group.radio_items.size(); ++ii) {
298 NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag: ii + 1];
299 [radioCell setTitle:base::SysUTF8ToNSString(radio_items[ii])];
302 // Layout radio group labels post-localization.
303 [GTMUILocalizerAndLayoutTweaker
304 wrapRadioGroupForWidth:allowBlockRadioGroup_];
305 CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
306 sizeToFitView:allowBlockRadioGroup_].height;
307 NSRect windowFrame = [[self window] frame];
308 windowFrame.size.height += radioDeltaY;
309 [[self window] setFrame:windowFrame display:NO];
312 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
313 title:(NSString*)title
315 referenceFrame:(NSRect)referenceFrame {
316 base::scoped_nsobject<HyperlinkButtonCell> cell(
317 [[HyperlinkButtonCell alloc] initTextCell:title]);
318 [cell.get() setAlignment:NSNaturalTextAlignment];
320 [cell.get() setImagePosition:NSImageLeft];
321 [cell.get() setImage:icon];
323 [cell.get() setImagePosition:NSNoImage];
325 [cell.get() setControlSize:NSSmallControlSize];
327 NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
328 // Cell must be set immediately after construction.
329 [button setCell:cell.get()];
331 // Size to fit the button and add a little extra padding for the small-text
332 // hyperlink button, which sizeToFit gets wrong.
333 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
334 NSRect buttonFrame = [button frame];
335 buttonFrame.size.width += 2;
337 // If the link text is too long, clamp it.
338 int maxWidth = NSWidth([[self bubble] frame]) - 2 * NSMinX(referenceFrame);
339 if (NSWidth(buttonFrame) > maxWidth)
340 buttonFrame.size.width = maxWidth;
342 [button setFrame:buttonFrame];
343 [button setTarget:self];
344 [button setAction:@selector(popupLinkClicked:)];
348 - (void)initializeBlockedPluginsList {
349 int delta = NSMinY([titleLabel_ frame]) -
350 NSMinY([blockedResourcesField_ frame]);
351 [blockedResourcesField_ removeFromSuperview];
352 NSRect frame = [[self window] frame];
353 frame.size.height -= delta;
354 [[self window] setFrame:frame display:NO];
357 - (void)initializePopupList {
358 // I didn't put the buttons into a NSMatrix because then they are only one
359 // entity in the key view loop. This way, one can tab through all of them.
360 const ContentSettingBubbleModel::PopupItems& popupItems =
361 contentSettingBubbleModel_->bubble_content().popup_items;
363 // Get the pre-resize frame of the radio group. Its origin is where the
364 // popup list should go.
365 NSRect radioFrame = [allowBlockRadioGroup_ frame];
367 // Make room for the popup list. The bubble view and its subviews autosize
368 // themselves when the window is enlarged.
369 // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
370 // so only 1 * kLinkOuterPadding more is needed.
371 int delta = popupItems.size() * kLinkLineHeight - kLinkPadding +
373 NSSize deltaSize = NSMakeSize(0, delta);
374 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
375 NSRect windowFrame = [[self window] frame];
376 windowFrame.size.height += deltaSize.height;
377 [[self window] setFrame:windowFrame display:NO];
379 // Create popup list.
380 int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
382 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
383 it(popupItems.begin()); it != popupItems.end(); ++it, ++row) {
384 NSImage* image = it->image.AsNSImage();
386 std::string title(it->title);
387 // The popup may not have committed a load yet, in which case it won't
388 // have a URL or title.
390 title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
393 NSMakeRect(NSMinX(radioFrame), topLinkY - kLinkLineHeight * row,
395 NSButton* button = [self
396 hyperlinkButtonWithFrame:linkFrame
397 title:base::SysUTF8ToNSString(title)
399 referenceFrame:radioFrame];
400 [[self bubble] addSubview:button];
401 popupLinks_[button] = row;
405 - (void)initializeGeoLists {
406 // Cocoa has its origin in the lower left corner. This means elements are
407 // added from bottom to top, which explains why loops run backwards and the
408 // order of operations is the other way than on Linux/Windows.
409 const ContentSettingBubbleModel::BubbleContent& content =
410 contentSettingBubbleModel_->bubble_content();
411 NSRect containerFrame = [contentsContainer_ frame];
412 NSRect frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
414 // "Clear" button / text field.
415 if (!content.custom_link.empty()) {
416 base::scoped_nsobject<NSControl> control;
417 if(content.custom_link_enabled) {
418 NSRect buttonFrame = NSMakeRect(0, 0,
419 NSWidth(containerFrame),
420 kGeoClearButtonHeight);
421 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
422 control.reset(button);
423 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
424 [button setTarget:self];
425 [button setAction:@selector(clearGeolocationForCurrentHost:)];
426 [button setBezelStyle:NSRoundRectBezelStyle];
427 SetControlSize(button, NSSmallControlSize);
430 // Add the notification that settings will be cleared on next reload.
431 control.reset([LabelWithFrame(
432 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
433 SetControlSize(control.get(), NSSmallControlSize);
436 // If the new control is wider than the container, widen the window.
437 CGFloat controlWidth = NSWidth([control frame]);
438 if (controlWidth > NSWidth(containerFrame)) {
439 NSRect windowFrame = [[self window] frame];
440 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
441 [[self window] setFrame:windowFrame display:NO];
442 // Fetch the updated sizes.
443 containerFrame = [contentsContainer_ frame];
444 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
448 [contentsContainer_ addSubview:control];
449 frame.origin.y = NSMaxY([control frame]) + kGeoPadding;
452 for (auto i = content.domain_lists.rbegin();
453 i != content.domain_lists.rend(); ++i) {
454 // Add all hosts in the current domain list.
455 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
456 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
457 SetControlSize(title, NSSmallControlSize);
458 [contentsContainer_ addSubview:title];
460 frame.origin.y = NSMaxY(frame) + kGeoHostPadding +
461 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
463 if (!i->hosts.empty())
464 frame.origin.y += kGeoPadding - kGeoHostPadding;
466 // Add the domain list's title.
468 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
469 SetControlSize(title, NSSmallControlSize);
470 [contentsContainer_ addSubview:title];
472 frame.origin.y = NSMaxY(frame) + kGeoPadding +
473 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
476 CGFloat containerHeight = frame.origin.y;
477 // Undo last padding.
478 if (!content.domain_lists.empty())
479 containerHeight -= kGeoPadding;
481 // Resize container to fit its subviews, and window to fit the container.
482 NSRect windowFrame = [[self window] frame];
483 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
484 [[self window] setFrame:windowFrame display:NO];
485 containerFrame.size.height = containerHeight;
486 [contentsContainer_ setFrame:containerFrame];
489 - (void)initializeMediaMenus {
490 const ContentSettingBubbleModel::MediaMenuMap& media_menus =
491 contentSettingBubbleModel_->bubble_content().media_menus;
493 // Calculate the longest width of the labels and menus menus to avoid
494 // truncation by the window's edge.
495 CGFloat maxLabelWidth = 0;
496 CGFloat maxMenuWidth = 0;
497 CGFloat maxMenuHeight = 0;
498 NSRect radioFrame = [allowBlockRadioGroup_ frame];
499 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator it(
500 media_menus.begin()); it != media_menus.end(); ++it) {
501 // |labelFrame| will be resized later on in this function.
502 NSRect labelFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
504 LabelWithFrame(base::SysUTF8ToNSString(it->second.label), labelFrame);
505 SetControlSize(label, NSSmallControlSize);
506 NSCell* cell = [label cell];
507 [cell setAlignment:NSRightTextAlignment];
508 [GTMUILocalizerAndLayoutTweaker sizeToFitView:label];
509 maxLabelWidth = std::max(maxLabelWidth, [label frame].size.width);
510 [[self bubble] addSubview:label];
512 // |buttonFrame| will be resized and repositioned later on.
513 NSRect buttonFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
514 base::scoped_nsobject<NSPopUpButton> button(
515 [[NSPopUpButton alloc] initWithFrame:buttonFrame]);
516 [button setTarget:self];
518 // Store the |label| and |button| into MediaMenuParts struct and build
519 // the popup menu from the menu model.
520 content_setting_bubble::MediaMenuParts* menuParts =
521 new content_setting_bubble::MediaMenuParts(it->first, label);
522 menuParts->model.reset(new ContentSettingMediaMenuModel(
523 it->first, contentSettingBubbleModel_.get(),
524 ContentSettingMediaMenuModel::MenuLabelChangedCallback()));
525 mediaMenus_[button] = menuParts;
526 CGFloat width = BuildPopUpMenuFromModel(button,
527 menuParts->model.get(),
528 it->second.selected_device.name,
529 it->second.disabled);
530 maxMenuWidth = std::max(maxMenuWidth, width);
532 [[self bubble] addSubview:button
533 positioned:NSWindowBelow
536 maxMenuHeight = std::max(maxMenuHeight, [button frame].size.height);
539 // Make room for the media menu(s) and enlarge the windows to fit the views.
540 // The bubble view and its subviews autosize themselves when the window is
542 int delta = media_menus.size() * maxMenuHeight +
543 (media_menus.size() - 1) * kMediaMenuElementVerticalPadding;
544 NSSize deltaSize = NSMakeSize(0, delta);
545 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
546 NSRect windowFrame = [[self window] frame];
547 windowFrame.size.height += deltaSize.height;
548 // If the media menus are wider than the window, widen the window.
549 CGFloat widthNeeded = maxLabelWidth + maxMenuWidth + 2 * NSMinX(radioFrame);
550 if (widthNeeded > windowFrame.size.width)
551 windowFrame.size.width = widthNeeded;
552 [[self window] setFrame:windowFrame display:NO];
554 // The radio group lies above the media menus, move the radio group up.
555 radioFrame.origin.y += delta;
556 [allowBlockRadioGroup_ setFrame:radioFrame];
558 // Resize and reposition the media menus layout.
559 CGFloat topMenuY = NSMinY(radioFrame) - kMediaMenuVerticalPadding;
560 maxMenuWidth = std::max(maxMenuWidth, kMinMediaMenuButtonWidth);
561 for (content_setting_bubble::MediaMenuPartsMap::const_iterator i =
562 mediaMenus_.begin(); i != mediaMenus_.end(); ++i) {
563 NSRect labelFrame = [i->second->label frame];
564 // Align the label text with the button text.
565 labelFrame.origin.y =
566 topMenuY + (maxMenuHeight - labelFrame.size.height) / 2 + 1;
567 labelFrame.size.width = maxLabelWidth;
568 [i->second->label setFrame:labelFrame];
569 NSRect menuFrame = [i->first frame];
570 menuFrame.origin.y = topMenuY;
571 menuFrame.origin.x = NSMinX(radioFrame) + maxLabelWidth;
572 menuFrame.size.width = maxMenuWidth;
573 menuFrame.size.height = maxMenuHeight;
574 [i->first setFrame:menuFrame];
575 topMenuY -= (maxMenuHeight + kMediaMenuElementVerticalPadding);
579 - (void)initializeMIDISysExLists {
580 const ContentSettingBubbleModel::BubbleContent& content =
581 contentSettingBubbleModel_->bubble_content();
582 NSRect containerFrame = [contentsContainer_ frame];
584 NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
586 // "Clear" button / text field.
587 if (!content.custom_link.empty()) {
588 base::scoped_nsobject<NSControl> control;
589 if (content.custom_link_enabled) {
590 NSRect buttonFrame = NSMakeRect(0, 0,
591 NSWidth(containerFrame),
592 kMIDISysExClearButtonHeight);
593 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
594 control.reset(button);
595 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
596 [button setTarget:self];
597 [button setAction:@selector(clearMIDISysExForCurrentHost:)];
598 [button setBezelStyle:NSRoundRectBezelStyle];
599 SetControlSize(button, NSSmallControlSize);
602 // Add the notification that settings will be cleared on next reload.
603 control.reset([LabelWithFrame(
604 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
605 SetControlSize(control.get(), NSSmallControlSize);
608 // If the new control is wider than the container, widen the window.
609 CGFloat controlWidth = NSWidth([control frame]);
610 if (controlWidth > NSWidth(containerFrame)) {
611 NSRect windowFrame = [[self window] frame];
612 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
613 [[self window] setFrame:windowFrame display:NO];
614 // Fetch the updated sizes.
615 containerFrame = [contentsContainer_ frame];
616 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
620 [contentsContainer_ addSubview:control];
621 frame.origin.y = NSMaxY([control frame]) + kMIDISysExPadding;
624 for (auto i = content.domain_lists.rbegin();
625 i != content.domain_lists.rend(); ++i) {
626 // Add all hosts in the current domain list.
627 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
628 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
629 SetControlSize(title, NSSmallControlSize);
630 [contentsContainer_ addSubview:title];
632 frame.origin.y = NSMaxY(frame) + kMIDISysExHostPadding +
633 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
635 if (!i->hosts.empty())
636 frame.origin.y += kMIDISysExPadding - kMIDISysExHostPadding;
638 // Add the domain list's title.
640 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
641 SetControlSize(title, NSSmallControlSize);
642 [contentsContainer_ addSubview:title];
644 frame.origin.y = NSMaxY(frame) + kMIDISysExPadding +
645 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
648 CGFloat containerHeight = frame.origin.y;
649 // Undo last padding.
650 if (!content.domain_lists.empty())
651 containerHeight -= kMIDISysExPadding;
653 // Resize container to fit its subviews, and window to fit the container.
654 NSRect windowFrame = [[self window] frame];
655 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
656 [[self window] setFrame:windowFrame display:NO];
657 containerFrame.size.height = containerHeight;
658 [contentsContainer_ setFrame:containerFrame];
661 - (void)sizeToFitLoadButton {
662 const ContentSettingBubbleModel::BubbleContent& content =
663 contentSettingBubbleModel_->bubble_content();
664 [loadButton_ setEnabled:content.custom_link_enabled];
666 // Resize horizontally to fit button if necessary.
667 NSRect windowFrame = [[self window] frame];
668 int widthNeeded = NSWidth([loadButton_ frame]) +
669 2 * NSMinX([loadButton_ frame]);
670 if (NSWidth(windowFrame) < widthNeeded) {
671 windowFrame.size.width = widthNeeded;
672 [[self window] setFrame:windowFrame display:NO];
676 - (void)initManageDoneButtons {
677 const ContentSettingBubbleModel::BubbleContent& content =
678 contentSettingBubbleModel_->bubble_content();
679 [manageButton_ setTitle:base::SysUTF8ToNSString(content.manage_link)];
680 [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageButton_];
682 CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
683 CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
684 NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
685 if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
688 // Resize window, autoresizing takes care of the rest.
689 NSSize size = NSMakeSize(requiredWidth - actualWidth, 0);
690 size = [[[self window] contentView] convertSize:size toView:nil];
691 NSRect frame = [[self window] frame];
692 frame.origin.x -= size.width;
693 frame.size.width += size.width;
694 [[self window] setFrame:frame display:NO];
697 - (void)awakeFromNib {
698 [super awakeFromNib];
700 [[self bubble] setArrowLocation:info_bubble::kTopRight];
702 // Adapt window size to bottom buttons. Do this before all other layouting.
703 [self initManageDoneButtons];
705 [self initializeTitle];
707 ContentSettingsType type = contentSettingBubbleModel_->content_type();
708 if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
709 [self sizeToFitLoadButton];
710 [self initializeBlockedPluginsList];
713 if (allowBlockRadioGroup_) // not bound in cookie bubble xib
714 [self initializeRadioGroup];
716 if (type == CONTENT_SETTINGS_TYPE_POPUPS)
717 [self initializePopupList];
718 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION)
719 [self initializeGeoLists];
720 if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM)
721 [self initializeMediaMenus];
722 if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
723 [self initializeMIDISysExLists];
726 ///////////////////////////////////////////////////////////////////////////////
727 // Actual application logic
729 - (IBAction)allowBlockToggled:(id)sender {
730 NSButtonCell *selectedCell = [sender selectedCell];
731 contentSettingBubbleModel_->OnRadioClicked([selectedCell tag] - 1);
734 - (void)popupLinkClicked:(id)sender {
735 content_setting_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
736 DCHECK(i != popupLinks_.end());
737 contentSettingBubbleModel_->OnPopupClicked(i->second);
740 - (void)clearGeolocationForCurrentHost:(id)sender {
741 contentSettingBubbleModel_->OnCustomLinkClicked();
745 - (void)clearMIDISysExForCurrentHost:(id)sender {
746 contentSettingBubbleModel_->OnCustomLinkClicked();
750 - (IBAction)showMoreInfo:(id)sender {
751 contentSettingBubbleModel_->OnCustomLinkClicked();
755 - (IBAction)load:(id)sender {
756 contentSettingBubbleModel_->OnCustomLinkClicked();
760 - (IBAction)learnMoreLinkClicked:(id)sender {
761 contentSettingBubbleModel_->OnManageLinkClicked();
764 - (IBAction)manageBlocking:(id)sender {
765 contentSettingBubbleModel_->OnManageLinkClicked();
768 - (IBAction)closeBubble:(id)sender {
769 contentSettingBubbleModel_->OnDoneClicked();
773 - (IBAction)mediaMenuChanged:(id)sender {
774 NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
775 content_setting_bubble::MediaMenuPartsMap::const_iterator it(
776 mediaMenus_.find(sender));
777 DCHECK(it != mediaMenus_.end());
778 NSInteger index = [[button selectedItem] tag];
780 SetTitleForPopUpButton(
781 button, base::SysUTF16ToNSString(it->second->model->GetLabelAt(index)));
783 it->second->model->ExecuteCommand(index, 0);
786 - (content_setting_bubble::MediaMenuPartsMap*)mediaMenus {
790 @end // ContentSettingBubbleController