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 #include "chrome/browser/ui/cocoa/extensions/media_galleries_dialog_cocoa.h"
7 #include "base/mac/scoped_nsobject.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/browser/ui/chrome_style.h"
10 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_alert.h"
11 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
12 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h"
13 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
14 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
15 #include "content/public/browser/web_contents.h"
16 #include "grit/generated_resources.h"
17 #import "ui/base/cocoa/flipped_view.h"
18 #import "ui/base/cocoa/menu_controller.h"
19 #import "ui/base/models/menu_model.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
23 // Controller for UI events on items in the media galleries dialog.
24 @interface MediaGalleriesCocoaController : NSObject {
26 MediaGalleriesDialogCocoa* dialog_;
29 @property(nonatomic, assign) MediaGalleriesDialogCocoa* dialog;
33 @implementation MediaGalleriesCocoaController
35 @synthesize dialog = dialog_;
37 - (void)onAcceptButton:(id)sender {
38 dialog_->OnAcceptClicked();
41 - (void)onCancelButton:(id)sender {
42 dialog_->OnCancelClicked();
45 - (void)onAddFolderClicked:(id)sender {
47 dialog_->OnAddFolderClicked();
50 - (void)onCheckboxToggled:(id)sender {
52 dialog_->OnCheckboxToggled(sender);
57 @interface MediaGalleriesCheckbox : NSButton {
59 MediaGalleriesDialogCocoa* dialog_;
60 MediaGalleryPrefId prefId_;
61 base::scoped_nsobject<MenuController> menu_controller_;
64 - (id)initWithFrame:(NSRect)frameRect
65 dialog:(MediaGalleriesDialogCocoa*)dialog
66 prefId:(MediaGalleryPrefId)prefId;
67 - (NSMenu*)menuForEvent:(NSEvent*)theEvent;
71 @implementation MediaGalleriesCheckbox
73 - (id)initWithFrame:(NSRect)frameRect
74 dialog:(MediaGalleriesDialogCocoa*)dialog
75 prefId:(MediaGalleryPrefId)prefId {
76 if ((self = [super initWithFrame:frameRect])) {
83 - (NSMenu*)menuForEvent:(NSEvent*)theEvent {
84 menu_controller_.reset(
85 [[MenuController alloc] initWithModel:dialog_->GetContextMenuModel(prefId_)
86 useWithPopUpButtonCell:NO]);
87 return [menu_controller_ menu];
94 const CGFloat kCheckboxMargin = 10;
95 const CGFloat kCheckboxMaxWidth = 440;
96 const CGFloat kScrollAreaHeight = 220;
98 NSString* GetUniqueIDForGallery(const MediaGalleryPrefInfo& gallery) {
99 return base::SysUTF8ToNSString(gallery.device_id + gallery.path.value());
104 MediaGalleriesDialogCocoa::MediaGalleriesDialogCocoa(
105 MediaGalleriesDialogController* controller,
106 MediaGalleriesCocoaController* cocoa_controller)
107 : controller_(controller),
109 cocoa_controller_([cocoa_controller retain]) {
110 [cocoa_controller_ setDialog:this];
112 alert_.reset([[ConstrainedWindowAlert alloc] init]);
114 [alert_ setMessageText:base::SysUTF16ToNSString(controller_->GetHeader())];
115 [alert_ setInformativeText:SysUTF16ToNSString(controller_->GetSubtext())];
116 [alert_ addButtonWithTitle:
117 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_CONFIRM)
118 keyEquivalent:kKeyEquivalentReturn
119 target:cocoa_controller_
120 action:@selector(onAcceptButton:)];
121 [alert_ addButtonWithTitle:
122 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_CANCEL)
123 keyEquivalent:kKeyEquivalentEscape
124 target:cocoa_controller_
125 action:@selector(onCancelButton:)];
126 [alert_ addButtonWithTitle:
127 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY)
128 keyEquivalent:kKeyEquivalentNone
129 target:cocoa_controller_
130 action:@selector(onAddFolderClicked:)];
131 [[alert_ closeButton] setTarget:cocoa_controller_];
132 [[alert_ closeButton] setAction:@selector(onCancelButton:)];
134 InitDialogControls();
136 // May be NULL during tests.
137 if (controller->web_contents()) {
138 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
139 [[CustomConstrainedWindowSheet alloc]
140 initWithCustomWindow:[alert_ window]]);
141 window_.reset(new ConstrainedWindowMac(
142 this, controller->web_contents(), sheet));
146 MediaGalleriesDialogCocoa::~MediaGalleriesDialogCocoa() {
149 void MediaGalleriesDialogCocoa::InitDialogControls() {
150 accessory_.reset([[NSBox alloc] init]);
151 [accessory_ setBoxType:NSBoxCustom];
152 [accessory_ setBorderType:NSLineBorder];
153 [accessory_ setBorderWidth:1];
154 [accessory_ setCornerRadius:0];
155 [accessory_ setTitlePosition:NSNoTitle];
156 [accessory_ setBorderColor:[NSColor colorWithCalibratedRed:0.625
161 base::scoped_nsobject<NSScrollView> scroll_view([[NSScrollView alloc]
162 initWithFrame:NSMakeRect(0, 0, kCheckboxMaxWidth, kScrollAreaHeight)]);
163 [scroll_view setHasVerticalScroller:YES];
164 [scroll_view setHasHorizontalScroller:NO];
165 [scroll_view setBorderType:NSNoBorder];
166 [scroll_view setAutohidesScrollers:YES];
167 [[accessory_ contentView] addSubview:scroll_view];
169 // Add gallery permission checkboxes inside the scrolling view.
170 checkbox_container_.reset([[FlippedView alloc] initWithFrame:NSZeroRect]);
171 checkboxes_.reset([[NSMutableArray alloc] init]);
172 [scroll_view setDocumentView:checkbox_container_];
174 CGFloat y_pos = kCheckboxMargin;
176 y_pos = CreateAttachedCheckboxes(y_pos, controller_->AttachedPermissions());
178 if (!controller_->UnattachedPermissions().empty()) {
179 y_pos = CreateCheckboxSeparator(y_pos);
181 y_pos = CreateUnattachedCheckboxes(
182 y_pos, controller_->UnattachedPermissions());
185 [checkbox_container_ setFrame:NSMakeRect(0, 0, kCheckboxMaxWidth, y_pos + 2)];
187 // Resize to pack the scroll view if possible.
188 NSRect scroll_frame = [scroll_view frame];
189 if (NSHeight(scroll_frame) > NSHeight([checkbox_container_ frame])) {
190 scroll_frame.size.height = NSHeight([checkbox_container_ frame]);
191 [scroll_view setFrame:scroll_frame];
194 [accessory_ setFrame:NSMakeRect(
195 0, 0, kCheckboxMaxWidth, NSHeight(scroll_frame))];
196 [alert_ setAccessoryView:accessory_];
198 // As a safeguard against the user skipping reading over the dialog and just
199 // confirming, the button will be unavailable for dialogs without any checks
200 // until the user toggles something.
201 [[[alert_ buttons] objectAtIndex:0] setEnabled:
202 controller_->HasPermittedGalleries()];
207 CGFloat MediaGalleriesDialogCocoa::CreateAttachedCheckboxes(
209 const MediaGalleriesDialogController::GalleryPermissionsVector&
211 y_pos += kCheckboxMargin;
213 for (MediaGalleriesDialogController::GalleryPermissionsVector::
214 const_iterator iter = permissions.begin();
215 iter != permissions.end(); iter++) {
216 const MediaGalleriesDialogController::GalleryPermission& permission = *iter;
217 UpdateGalleryCheckbox(permission.pref_info, permission.allowed, y_pos);
218 y_pos = NSMaxY([[checkboxes_ lastObject] frame]) + kCheckboxMargin;
224 // Add checkboxes for galleries that aren't available (i.e. removable
225 // volumes that are not currently attached).
226 CGFloat MediaGalleriesDialogCocoa::CreateUnattachedCheckboxes(
228 const MediaGalleriesDialogController::GalleryPermissionsVector&
230 y_pos += kCheckboxMargin;
232 for (MediaGalleriesDialogController::GalleryPermissionsVector::
233 const_iterator iter = permissions.begin();
234 iter != permissions.end(); iter++) {
235 const MediaGalleriesDialogController::GalleryPermission& permission = *iter;
236 UpdateGalleryCheckbox(permission.pref_info, permission.allowed, y_pos);
237 y_pos = NSMaxY([[checkboxes_ lastObject] frame]) + kCheckboxMargin;
243 CGFloat MediaGalleriesDialogCocoa::CreateCheckboxSeparator(CGFloat y_pos) {
244 base::scoped_nsobject<NSBox> separator(
245 [[NSBox alloc] initWithFrame:NSMakeRect(
246 0, y_pos + kCheckboxMargin * 0.5, kCheckboxMaxWidth, 1.0)]);
247 [separator setBoxType:NSBoxSeparator];
248 [separator setBorderType:NSLineBorder];
249 [separator setAlphaValue:0.2];
250 [checkbox_container_ addSubview:separator];
251 y_pos += kCheckboxMargin * 0.5 + 4;
253 base::scoped_nsobject<NSTextField> unattached_label(
254 [[NSTextField alloc] initWithFrame:NSZeroRect]);
255 [unattached_label setEditable:NO];
256 [unattached_label setSelectable:NO];
257 [unattached_label setBezeled:NO];
258 [unattached_label setAttributedStringValue:
259 constrained_window::GetAttributedLabelString(
260 base::SysUTF16ToNSString(
261 controller_->GetUnattachedLocationsHeader()),
262 chrome_style::kTextFontStyle,
263 NSNaturalTextAlignment,
264 NSLineBreakByClipping
266 [unattached_label sizeToFit];
267 NSSize unattached_label_size = [unattached_label frame].size;
268 [unattached_label setFrame:NSMakeRect(
269 kCheckboxMargin, y_pos + kCheckboxMargin,
270 kCheckboxMaxWidth, unattached_label_size.height)];
271 [checkbox_container_ addSubview:unattached_label];
272 y_pos = NSMaxY([unattached_label frame]) + kCheckboxMargin;
277 void MediaGalleriesDialogCocoa::OnAcceptClicked() {
281 window_->CloseWebContentsModalDialog();
284 void MediaGalleriesDialogCocoa::OnCancelClicked() {
286 window_->CloseWebContentsModalDialog();
289 void MediaGalleriesDialogCocoa::OnAddFolderClicked() {
290 controller_->OnAddFolderClicked();
293 void MediaGalleriesDialogCocoa::OnCheckboxToggled(NSButton* checkbox) {
294 [[[alert_ buttons] objectAtIndex:0] setEnabled:YES];
296 const MediaGalleriesDialogController::GalleryPermissionsVector&
297 attached_permissions = controller_->AttachedPermissions();
298 for (MediaGalleriesDialogController::GalleryPermissionsVector::
299 const_reverse_iterator iter = attached_permissions.rbegin();
300 iter != attached_permissions.rend(); iter++) {
301 const MediaGalleryPrefInfo* gallery = &iter->pref_info;
302 NSString* unique_id = GetUniqueIDForGallery(*gallery);
303 if ([[[checkbox cell] representedObject] isEqual:unique_id]) {
304 controller_->DidToggleGalleryId(gallery->pref_id,
305 [checkbox state] == NSOnState);
310 const MediaGalleriesDialogController::GalleryPermissionsVector&
311 unattached_permissions = controller_->UnattachedPermissions();
312 for (MediaGalleriesDialogController::GalleryPermissionsVector::
313 const_reverse_iterator iter = unattached_permissions.rbegin();
314 iter != unattached_permissions.rend(); iter++) {
315 const MediaGalleryPrefInfo* gallery = &iter->pref_info;
316 NSString* unique_id = GetUniqueIDForGallery(*gallery);
317 if ([[[checkbox cell] representedObject] isEqual:unique_id]) {
318 controller_->DidToggleGalleryId(gallery->pref_id,
319 [checkbox state] == NSOnState);
326 void MediaGalleriesDialogCocoa::UpdateGalleryCheckbox(
327 const MediaGalleryPrefInfo& gallery,
330 base::scoped_nsobject<MediaGalleriesCheckbox> checkbox(
331 [[MediaGalleriesCheckbox alloc] initWithFrame:NSZeroRect
333 prefId:gallery.pref_id]);
334 NSString* unique_id = GetUniqueIDForGallery(gallery);
335 [[checkbox cell] setRepresentedObject:unique_id];
336 [[checkbox cell] setLineBreakMode:NSLineBreakByTruncatingMiddle];
337 [checkbox setButtonType:NSSwitchButton];
338 [checkbox setTarget:cocoa_controller_];
339 [checkbox setAction:@selector(onCheckboxToggled:)];
340 [checkboxes_ addObject:checkbox];
342 // TODO(gbillock): Would be nice to add middle text elide behavior here.
343 [checkbox setTitle:base::SysUTF16ToNSString(
344 gallery.GetGalleryDisplayName())];
345 [checkbox setToolTip:base::SysUTF16ToNSString(gallery.GetGalleryTooltip())];
346 [checkbox setState:permitted ? NSOnState : NSOffState];
348 [checkbox sizeToFit];
349 NSRect rect = [checkbox bounds];
350 rect.origin.y = y_pos;
351 rect.origin.x = kCheckboxMargin;
352 rect.size.width = std::min(NSWidth(rect), kCheckboxMaxWidth);
353 [checkbox setFrame:rect];
354 [checkbox_container_ addSubview:checkbox];
356 base::scoped_nsobject<NSTextField> details(
357 [[NSTextField alloc] initWithFrame:NSZeroRect]);
358 [details setEditable:NO];
359 [details setSelectable:NO];
360 [details setBezeled:NO];
361 [details setAttributedStringValue:
362 constrained_window::GetAttributedLabelString(
363 base::SysUTF16ToNSString(gallery.GetGalleryAdditionalDetails()),
364 chrome_style::kTextFontStyle,
365 NSNaturalTextAlignment,
366 NSLineBreakByClipping
368 [details setTextColor:[NSColor colorWithCalibratedRed:0.625
373 NSRect details_rect = [details bounds];
374 details_rect.origin.y = y_pos - 1;
375 details_rect.origin.x = kCheckboxMargin + rect.size.width + kCheckboxMargin;
376 details_rect.size.width = kCheckboxMaxWidth - details_rect.origin.x;
377 [details setFrame:details_rect];
379 [checkbox_container_ addSubview:details];
382 void MediaGalleriesDialogCocoa::UpdateGalleries() {
383 InitDialogControls();
386 void MediaGalleriesDialogCocoa::OnConstrainedWindowClosed(
387 ConstrainedWindowMac* window) {
388 controller_->DialogFinished(accepted_);
391 ui::MenuModel* MediaGalleriesDialogCocoa::GetContextMenuModel(
392 MediaGalleryPrefId prefid) {
393 return controller_->GetContextMenuModel(prefid);
397 MediaGalleriesDialog* MediaGalleriesDialog::Create(
398 MediaGalleriesDialogController* controller) {
399 base::scoped_nsobject<MediaGalleriesCocoaController> cocoa_controller(
400 [[MediaGalleriesCocoaController alloc] init]);
401 return new MediaGalleriesDialogCocoa(controller, cocoa_controller);