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/GTM/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 case CONTENT_SETTINGS_TYPE_SAVE_PASSWORD:
243 nibPath = @"ContentBlockedSavePassword"; break;
244 // These content types have no bubble:
245 case CONTENT_SETTINGS_TYPE_DEFAULT:
246 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
247 case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
248 case CONTENT_SETTINGS_TYPE_FULLSCREEN:
249 case CONTENT_SETTINGS_TYPE_MOUSELOCK:
250 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
251 case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
252 case CONTENT_SETTINGS_NUM_TYPES:
255 if ((self = [super initWithWindowNibPath:nibPath
256 parentWindow:parentWindow
257 anchoredAt:anchoredAt])) {
258 contentSettingBubbleModel_.reset(model.release());
259 [self showWindow:nil];
265 STLDeleteValues(&mediaMenus_);
269 - (void)initializeTitle {
273 NSString* label = base::SysUTF8ToNSString(
274 contentSettingBubbleModel_->bubble_content().title);
275 [titleLabel_ setStringValue:label];
277 // Layout title post-localization.
278 CGFloat deltaY = [GTMUILocalizerAndLayoutTweaker
279 sizeToFitFixedWidthTextField:titleLabel_];
280 NSRect windowFrame = [[self window] frame];
281 windowFrame.size.height += deltaY;
282 [[self window] setFrame:windowFrame display:NO];
283 NSRect titleFrame = [titleLabel_ frame];
284 titleFrame.origin.y -= deltaY;
285 [titleLabel_ setFrame:titleFrame];
288 - (void)initializeRadioGroup {
289 // NOTE! Tags in the xib files must match the order of the radio buttons
290 // passed in the radio_group and be 1-based, not 0-based.
291 const ContentSettingBubbleModel::RadioGroup& radio_group =
292 contentSettingBubbleModel_->bubble_content().radio_group;
294 // Select appropriate radio button.
295 [allowBlockRadioGroup_ selectCellWithTag: radio_group.default_item + 1];
297 const ContentSettingBubbleModel::RadioItems& radio_items =
298 radio_group.radio_items;
299 for (size_t ii = 0; ii < radio_group.radio_items.size(); ++ii) {
300 NSCell* radioCell = [allowBlockRadioGroup_ cellWithTag: ii + 1];
301 [radioCell setTitle:base::SysUTF8ToNSString(radio_items[ii])];
304 // Layout radio group labels post-localization.
305 [GTMUILocalizerAndLayoutTweaker
306 wrapRadioGroupForWidth:allowBlockRadioGroup_];
307 CGFloat radioDeltaY = [GTMUILocalizerAndLayoutTweaker
308 sizeToFitView:allowBlockRadioGroup_].height;
309 NSRect windowFrame = [[self window] frame];
310 windowFrame.size.height += radioDeltaY;
311 [[self window] setFrame:windowFrame display:NO];
314 - (NSButton*)hyperlinkButtonWithFrame:(NSRect)frame
315 title:(NSString*)title
317 referenceFrame:(NSRect)referenceFrame {
318 base::scoped_nsobject<HyperlinkButtonCell> cell(
319 [[HyperlinkButtonCell alloc] initTextCell:title]);
320 [cell.get() setAlignment:NSNaturalTextAlignment];
322 [cell.get() setImagePosition:NSImageLeft];
323 [cell.get() setImage:icon];
325 [cell.get() setImagePosition:NSNoImage];
327 [cell.get() setControlSize:NSSmallControlSize];
329 NSButton* button = [[[NSButton alloc] initWithFrame:frame] autorelease];
330 // Cell must be set immediately after construction.
331 [button setCell:cell.get()];
333 // Size to fit the button and add a little extra padding for the small-text
334 // hyperlink button, which sizeToFit gets wrong.
335 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
336 NSRect buttonFrame = [button frame];
337 buttonFrame.size.width += 2;
339 // If the link text is too long, clamp it.
340 int maxWidth = NSWidth([[self bubble] frame]) - 2 * NSMinX(referenceFrame);
341 if (NSWidth(buttonFrame) > maxWidth)
342 buttonFrame.size.width = maxWidth;
344 [button setFrame:buttonFrame];
345 [button setTarget:self];
346 [button setAction:@selector(popupLinkClicked:)];
350 - (void)initializeBlockedPluginsList {
351 NSMutableArray* pluginArray = [NSMutableArray array];
352 const std::set<std::string>& plugins =
353 contentSettingBubbleModel_->bubble_content().resource_identifiers;
354 if (plugins.empty()) {
355 int delta = NSMinY([titleLabel_ frame]) -
356 NSMinY([blockedResourcesField_ frame]);
357 [blockedResourcesField_ removeFromSuperview];
358 NSRect frame = [[self window] frame];
359 frame.size.height -= delta;
360 [[self window] setFrame:frame display:NO];
362 PluginFinder* finder = PluginFinder::GetInstance();
363 for (std::set<std::string>::iterator it = plugins.begin();
364 it != plugins.end(); ++it) {
366 SysUTF16ToNSString(finder->FindPluginNameWithIdentifier(*it));
367 [pluginArray addObject:name];
369 [blockedResourcesField_
370 setStringValue:[pluginArray componentsJoinedByString:@"\n"]];
371 [GTMUILocalizerAndLayoutTweaker
372 sizeToFitFixedWidthTextField:blockedResourcesField_];
376 - (void)initializePopupList {
377 // I didn't put the buttons into a NSMatrix because then they are only one
378 // entity in the key view loop. This way, one can tab through all of them.
379 const ContentSettingBubbleModel::PopupItems& popupItems =
380 contentSettingBubbleModel_->bubble_content().popup_items;
382 // Get the pre-resize frame of the radio group. Its origin is where the
383 // popup list should go.
384 NSRect radioFrame = [allowBlockRadioGroup_ frame];
386 // Make room for the popup list. The bubble view and its subviews autosize
387 // themselves when the window is enlarged.
388 // Heading and radio box are already 1 * kLinkOuterPadding apart in the nib,
389 // so only 1 * kLinkOuterPadding more is needed.
390 int delta = popupItems.size() * kLinkLineHeight - kLinkPadding +
392 NSSize deltaSize = NSMakeSize(0, delta);
393 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
394 NSRect windowFrame = [[self window] frame];
395 windowFrame.size.height += deltaSize.height;
396 [[self window] setFrame:windowFrame display:NO];
398 // Create popup list.
399 int topLinkY = NSMaxY(radioFrame) + delta - kLinkHeight;
401 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
402 it(popupItems.begin()); it != popupItems.end(); ++it, ++row) {
403 NSImage* image = it->image.AsNSImage();
405 std::string title(it->title);
406 // The popup may not have committed a load yet, in which case it won't
407 // have a URL or title.
409 title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
412 NSMakeRect(NSMinX(radioFrame), topLinkY - kLinkLineHeight * row,
414 NSButton* button = [self
415 hyperlinkButtonWithFrame:linkFrame
416 title:base::SysUTF8ToNSString(title)
418 referenceFrame:radioFrame];
419 [[self bubble] addSubview:button];
420 popupLinks_[button] = row;
424 - (void)initializeGeoLists {
425 // Cocoa has its origin in the lower left corner. This means elements are
426 // added from bottom to top, which explains why loops run backwards and the
427 // order of operations is the other way than on Linux/Windows.
428 const ContentSettingBubbleModel::BubbleContent& content =
429 contentSettingBubbleModel_->bubble_content();
430 NSRect containerFrame = [contentsContainer_ frame];
431 NSRect frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
433 // "Clear" button / text field.
434 if (!content.custom_link.empty()) {
435 base::scoped_nsobject<NSControl> control;
436 if(content.custom_link_enabled) {
437 NSRect buttonFrame = NSMakeRect(0, 0,
438 NSWidth(containerFrame),
439 kGeoClearButtonHeight);
440 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
441 control.reset(button);
442 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
443 [button setTarget:self];
444 [button setAction:@selector(clearGeolocationForCurrentHost:)];
445 [button setBezelStyle:NSRoundRectBezelStyle];
446 SetControlSize(button, NSSmallControlSize);
449 // Add the notification that settings will be cleared on next reload.
450 control.reset([LabelWithFrame(
451 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
452 SetControlSize(control.get(), NSSmallControlSize);
455 // If the new control is wider than the container, widen the window.
456 CGFloat controlWidth = NSWidth([control frame]);
457 if (controlWidth > NSWidth(containerFrame)) {
458 NSRect windowFrame = [[self window] frame];
459 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
460 [[self window] setFrame:windowFrame display:NO];
461 // Fetch the updated sizes.
462 containerFrame = [contentsContainer_ frame];
463 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kGeoLabelHeight);
467 [contentsContainer_ addSubview:control];
468 frame.origin.y = NSMaxY([control frame]) + kGeoPadding;
471 for (auto i = content.domain_lists.rbegin();
472 i != content.domain_lists.rend(); ++i) {
473 // Add all hosts in the current domain list.
474 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
475 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
476 SetControlSize(title, NSSmallControlSize);
477 [contentsContainer_ addSubview:title];
479 frame.origin.y = NSMaxY(frame) + kGeoHostPadding +
480 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
482 if (!i->hosts.empty())
483 frame.origin.y += kGeoPadding - kGeoHostPadding;
485 // Add the domain list's title.
487 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
488 SetControlSize(title, NSSmallControlSize);
489 [contentsContainer_ addSubview:title];
491 frame.origin.y = NSMaxY(frame) + kGeoPadding +
492 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
495 CGFloat containerHeight = frame.origin.y;
496 // Undo last padding.
497 if (!content.domain_lists.empty())
498 containerHeight -= kGeoPadding;
500 // Resize container to fit its subviews, and window to fit the container.
501 NSRect windowFrame = [[self window] frame];
502 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
503 [[self window] setFrame:windowFrame display:NO];
504 containerFrame.size.height = containerHeight;
505 [contentsContainer_ setFrame:containerFrame];
508 - (void)initializeMediaMenus {
509 const ContentSettingBubbleModel::MediaMenuMap& media_menus =
510 contentSettingBubbleModel_->bubble_content().media_menus;
512 // Calculate the longest width of the labels and menus menus to avoid
513 // truncation by the window's edge.
514 CGFloat maxLabelWidth = 0;
515 CGFloat maxMenuWidth = 0;
516 CGFloat maxMenuHeight = 0;
517 NSRect radioFrame = [allowBlockRadioGroup_ frame];
518 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator it(
519 media_menus.begin()); it != media_menus.end(); ++it) {
520 // |labelFrame| will be resized later on in this function.
521 NSRect labelFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
523 LabelWithFrame(base::SysUTF8ToNSString(it->second.label), labelFrame);
524 SetControlSize(label, NSSmallControlSize);
525 NSCell* cell = [label cell];
526 [cell setAlignment:NSRightTextAlignment];
527 [GTMUILocalizerAndLayoutTweaker sizeToFitView:label];
528 maxLabelWidth = std::max(maxLabelWidth, [label frame].size.width);
529 [[self bubble] addSubview:label];
531 // |buttonFrame| will be resized and repositioned later on.
532 NSRect buttonFrame = NSMakeRect(NSMinX(radioFrame), 0, 0, 0);
533 base::scoped_nsobject<NSPopUpButton> button(
534 [[NSPopUpButton alloc] initWithFrame:buttonFrame]);
535 [button setTarget:self];
537 // Store the |label| and |button| into MediaMenuParts struct and build
538 // the popup menu from the menu model.
539 content_setting_bubble::MediaMenuParts* menuParts =
540 new content_setting_bubble::MediaMenuParts(it->first, label);
541 menuParts->model.reset(new ContentSettingMediaMenuModel(
542 it->first, contentSettingBubbleModel_.get(),
543 ContentSettingMediaMenuModel::MenuLabelChangedCallback()));
544 mediaMenus_[button] = menuParts;
545 CGFloat width = BuildPopUpMenuFromModel(button,
546 menuParts->model.get(),
547 it->second.selected_device.name,
548 it->second.disabled);
549 maxMenuWidth = std::max(maxMenuWidth, width);
551 [[self bubble] addSubview:button
552 positioned:NSWindowBelow
555 maxMenuHeight = std::max(maxMenuHeight, [button frame].size.height);
558 // Make room for the media menu(s) and enlarge the windows to fit the views.
559 // The bubble view and its subviews autosize themselves when the window is
561 int delta = media_menus.size() * maxMenuHeight +
562 (media_menus.size() - 1) * kMediaMenuElementVerticalPadding;
563 NSSize deltaSize = NSMakeSize(0, delta);
564 deltaSize = [[[self window] contentView] convertSize:deltaSize toView:nil];
565 NSRect windowFrame = [[self window] frame];
566 windowFrame.size.height += deltaSize.height;
567 // If the media menus are wider than the window, widen the window.
568 CGFloat widthNeeded = maxLabelWidth + maxMenuWidth + 2 * NSMinX(radioFrame);
569 if (widthNeeded > windowFrame.size.width)
570 windowFrame.size.width = widthNeeded;
571 [[self window] setFrame:windowFrame display:NO];
573 // The radio group lies above the media menus, move the radio group up.
574 radioFrame.origin.y += delta;
575 [allowBlockRadioGroup_ setFrame:radioFrame];
577 // Resize and reposition the media menus layout.
578 CGFloat topMenuY = NSMinY(radioFrame) - kMediaMenuVerticalPadding;
579 maxMenuWidth = std::max(maxMenuWidth, kMinMediaMenuButtonWidth);
580 for (content_setting_bubble::MediaMenuPartsMap::const_iterator i =
581 mediaMenus_.begin(); i != mediaMenus_.end(); ++i) {
582 NSRect labelFrame = [i->second->label frame];
583 // Align the label text with the button text.
584 labelFrame.origin.y =
585 topMenuY + (maxMenuHeight - labelFrame.size.height) / 2 + 1;
586 labelFrame.size.width = maxLabelWidth;
587 [i->second->label setFrame:labelFrame];
588 NSRect menuFrame = [i->first frame];
589 menuFrame.origin.y = topMenuY;
590 menuFrame.origin.x = NSMinX(radioFrame) + maxLabelWidth;
591 menuFrame.size.width = maxMenuWidth;
592 menuFrame.size.height = maxMenuHeight;
593 [i->first setFrame:menuFrame];
594 topMenuY -= (maxMenuHeight + kMediaMenuElementVerticalPadding);
598 - (void)initializeMIDISysExLists {
599 const ContentSettingBubbleModel::BubbleContent& content =
600 contentSettingBubbleModel_->bubble_content();
601 NSRect containerFrame = [contentsContainer_ frame];
603 NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
605 // "Clear" button / text field.
606 if (!content.custom_link.empty()) {
607 base::scoped_nsobject<NSControl> control;
608 if (content.custom_link_enabled) {
609 NSRect buttonFrame = NSMakeRect(0, 0,
610 NSWidth(containerFrame),
611 kMIDISysExClearButtonHeight);
612 NSButton* button = [[NSButton alloc] initWithFrame:buttonFrame];
613 control.reset(button);
614 [button setTitle:base::SysUTF8ToNSString(content.custom_link)];
615 [button setTarget:self];
616 [button setAction:@selector(clearMIDISysExForCurrentHost:)];
617 [button setBezelStyle:NSRoundRectBezelStyle];
618 SetControlSize(button, NSSmallControlSize);
621 // Add the notification that settings will be cleared on next reload.
622 control.reset([LabelWithFrame(
623 base::SysUTF8ToNSString(content.custom_link), frame) retain]);
624 SetControlSize(control.get(), NSSmallControlSize);
627 // If the new control is wider than the container, widen the window.
628 CGFloat controlWidth = NSWidth([control frame]);
629 if (controlWidth > NSWidth(containerFrame)) {
630 NSRect windowFrame = [[self window] frame];
631 windowFrame.size.width += controlWidth - NSWidth(containerFrame);
632 [[self window] setFrame:windowFrame display:NO];
633 // Fetch the updated sizes.
634 containerFrame = [contentsContainer_ frame];
635 frame = NSMakeRect(0, 0, NSWidth(containerFrame), kMIDISysExLabelHeight);
639 [contentsContainer_ addSubview:control];
640 frame.origin.y = NSMaxY([control frame]) + kMIDISysExPadding;
643 for (auto i = content.domain_lists.rbegin();
644 i != content.domain_lists.rend(); ++i) {
645 // Add all hosts in the current domain list.
646 for (auto j = i->hosts.rbegin(); j != i->hosts.rend(); ++j) {
647 NSTextField* title = LabelWithFrame(base::SysUTF8ToNSString(*j), frame);
648 SetControlSize(title, NSSmallControlSize);
649 [contentsContainer_ addSubview:title];
651 frame.origin.y = NSMaxY(frame) + kMIDISysExHostPadding +
652 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
654 if (!i->hosts.empty())
655 frame.origin.y += kMIDISysExPadding - kMIDISysExHostPadding;
657 // Add the domain list's title.
659 LabelWithFrame(base::SysUTF8ToNSString(i->title), frame);
660 SetControlSize(title, NSSmallControlSize);
661 [contentsContainer_ addSubview:title];
663 frame.origin.y = NSMaxY(frame) + kMIDISysExPadding +
664 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title];
667 CGFloat containerHeight = frame.origin.y;
668 // Undo last padding.
669 if (!content.domain_lists.empty())
670 containerHeight -= kMIDISysExPadding;
672 // Resize container to fit its subviews, and window to fit the container.
673 NSRect windowFrame = [[self window] frame];
674 windowFrame.size.height += containerHeight - NSHeight(containerFrame);
675 [[self window] setFrame:windowFrame display:NO];
676 containerFrame.size.height = containerHeight;
677 [contentsContainer_ setFrame:containerFrame];
680 - (void)sizeToFitLoadButton {
681 const ContentSettingBubbleModel::BubbleContent& content =
682 contentSettingBubbleModel_->bubble_content();
683 [loadButton_ setEnabled:content.custom_link_enabled];
685 // Resize horizontally to fit button if necessary.
686 NSRect windowFrame = [[self window] frame];
687 int widthNeeded = NSWidth([loadButton_ frame]) +
688 2 * NSMinX([loadButton_ frame]);
689 if (NSWidth(windowFrame) < widthNeeded) {
690 windowFrame.size.width = widthNeeded;
691 [[self window] setFrame:windowFrame display:NO];
695 - (void)initManageDoneButtons {
696 const ContentSettingBubbleModel::BubbleContent& content =
697 contentSettingBubbleModel_->bubble_content();
698 [manageButton_ setTitle:base::SysUTF8ToNSString(content.manage_link)];
699 [GTMUILocalizerAndLayoutTweaker sizeToFitView:manageButton_];
701 CGFloat actualWidth = NSWidth([[[self window] contentView] frame]);
702 CGFloat requiredWidth = NSMaxX([manageButton_ frame]) + kManageDonePadding +
703 NSWidth([[doneButton_ superview] frame]) - NSMinX([doneButton_ frame]);
704 if (requiredWidth <= actualWidth || !doneButton_ || !manageButton_)
707 // Resize window, autoresizing takes care of the rest.
708 NSSize size = NSMakeSize(requiredWidth - actualWidth, 0);
709 size = [[[self window] contentView] convertSize:size toView:nil];
710 NSRect frame = [[self window] frame];
711 frame.origin.x -= size.width;
712 frame.size.width += size.width;
713 [[self window] setFrame:frame display:NO];
716 - (void)awakeFromNib {
717 [super awakeFromNib];
719 [[self bubble] setArrowLocation:info_bubble::kTopRight];
721 // Adapt window size to bottom buttons. Do this before all other layouting.
722 [self initManageDoneButtons];
724 [self initializeTitle];
726 ContentSettingsType type = contentSettingBubbleModel_->content_type();
727 if (type == CONTENT_SETTINGS_TYPE_PLUGINS) {
728 [self sizeToFitLoadButton];
729 [self initializeBlockedPluginsList];
732 if (allowBlockRadioGroup_) // not bound in cookie bubble xib
733 [self initializeRadioGroup];
735 if (type == CONTENT_SETTINGS_TYPE_POPUPS)
736 [self initializePopupList];
737 if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION)
738 [self initializeGeoLists];
739 if (type == CONTENT_SETTINGS_TYPE_MEDIASTREAM)
740 [self initializeMediaMenus];
741 if (type == CONTENT_SETTINGS_TYPE_MIDI_SYSEX)
742 [self initializeMIDISysExLists];
745 ///////////////////////////////////////////////////////////////////////////////
746 // Actual application logic
748 - (IBAction)allowBlockToggled:(id)sender {
749 NSButtonCell *selectedCell = [sender selectedCell];
750 contentSettingBubbleModel_->OnRadioClicked([selectedCell tag] - 1);
753 - (void)popupLinkClicked:(id)sender {
754 content_setting_bubble::PopupLinks::iterator i(popupLinks_.find(sender));
755 DCHECK(i != popupLinks_.end());
756 contentSettingBubbleModel_->OnPopupClicked(i->second);
759 - (void)clearGeolocationForCurrentHost:(id)sender {
760 contentSettingBubbleModel_->OnCustomLinkClicked();
764 - (void)clearMIDISysExForCurrentHost:(id)sender {
765 contentSettingBubbleModel_->OnCustomLinkClicked();
769 - (IBAction)showMoreInfo:(id)sender {
770 contentSettingBubbleModel_->OnCustomLinkClicked();
774 - (IBAction)load:(id)sender {
775 contentSettingBubbleModel_->OnCustomLinkClicked();
779 - (IBAction)learnMoreLinkClicked:(id)sender {
780 contentSettingBubbleModel_->OnManageLinkClicked();
783 - (IBAction)manageBlocking:(id)sender {
784 contentSettingBubbleModel_->OnManageLinkClicked();
787 - (IBAction)saveBubble:(id)sender {
788 contentSettingBubbleModel_->OnSaveClicked();
792 - (IBAction)blacklistBubble:(id)sender {
793 contentSettingBubbleModel_->OnCancelClicked();
797 - (IBAction)closeBubble:(id)sender {
798 contentSettingBubbleModel_->OnDoneClicked();
802 - (IBAction)mediaMenuChanged:(id)sender {
803 NSPopUpButton* button = static_cast<NSPopUpButton*>(sender);
804 content_setting_bubble::MediaMenuPartsMap::const_iterator it(
805 mediaMenus_.find(sender));
806 DCHECK(it != mediaMenus_.end());
807 NSInteger index = [[button selectedItem] tag];
809 SetTitleForPopUpButton(
810 button, base::SysUTF16ToNSString(it->second->model->GetLabelAt(index)));
812 it->second->model->ExecuteCommand(index, 0);
815 - (content_setting_bubble::MediaMenuPartsMap*)mediaMenus {
819 @end // ContentSettingBubbleController