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(assign, nonatomic) 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);
58 @interface MediaGalleriesCheckbox : NSButton {
60 MediaGalleriesDialogCocoa* dialog_; // |dialog_| owns |this|.
61 MediaGalleryPrefId prefId_;
62 base::scoped_nsobject<MenuController> menuController_;
65 - (id)initWithFrame:(NSRect)frameRect
66 dialog:(MediaGalleriesDialogCocoa*)dialog
67 prefId:(MediaGalleryPrefId)prefId;
68 - (NSMenu*)menuForEvent:(NSEvent*)theEvent;
72 @implementation MediaGalleriesCheckbox
74 - (id)initWithFrame:(NSRect)frameRect
75 dialog:(MediaGalleriesDialogCocoa*)dialog
76 prefId:(MediaGalleryPrefId)prefId {
77 if ((self = [super initWithFrame:frameRect])) {
84 - (NSMenu*)menuForEvent:(NSEvent*)theEvent {
85 menuController_.reset(
86 [[MenuController alloc] initWithModel:dialog_->GetContextMenu(prefId_)
87 useWithPopUpButtonCell:NO]);
88 return [menuController_ menu];
95 const CGFloat kCheckboxMargin = 10;
96 const CGFloat kCheckboxMaxWidth = 440;
97 const CGFloat kScrollAreaHeight = 220;
99 NSString* GetUniqueIDForGallery(const MediaGalleryPrefInfo& gallery) {
100 return base::SysUTF8ToNSString(gallery.device_id + gallery.path.value());
105 MediaGalleriesDialogCocoa::MediaGalleriesDialogCocoa(
106 MediaGalleriesDialogController* controller,
107 MediaGalleriesCocoaController* cocoa_controller)
108 : controller_(controller),
110 cocoa_controller_([cocoa_controller retain]) {
111 [cocoa_controller_ setDialog:this];
113 alert_.reset([[ConstrainedWindowAlert alloc] init]);
115 [alert_ setMessageText:base::SysUTF16ToNSString(controller_->GetHeader())];
116 [alert_ setInformativeText:
117 base::SysUTF16ToNSString(controller_->GetSubtext())];
118 [alert_ addButtonWithTitle:
119 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_CONFIRM)
120 keyEquivalent:kKeyEquivalentReturn
121 target:cocoa_controller_
122 action:@selector(onAcceptButton:)];
123 [alert_ addButtonWithTitle:
124 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_CANCEL)
125 keyEquivalent:kKeyEquivalentEscape
126 target:cocoa_controller_
127 action:@selector(onCancelButton:)];
128 [alert_ addButtonWithTitle:
129 l10n_util::GetNSString(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY)
130 keyEquivalent:kKeyEquivalentNone
131 target:cocoa_controller_
132 action:@selector(onAddFolderClicked:)];
133 [[alert_ closeButton] setTarget:cocoa_controller_];
134 [[alert_ closeButton] setAction:@selector(onCancelButton:)];
136 InitDialogControls();
138 // May be NULL during tests.
139 if (controller->web_contents()) {
140 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
141 [[CustomConstrainedWindowSheet alloc]
142 initWithCustomWindow:[alert_ window]]);
143 window_.reset(new ConstrainedWindowMac(
144 this, controller->web_contents(), sheet));
148 MediaGalleriesDialogCocoa::~MediaGalleriesDialogCocoa() {
151 void MediaGalleriesDialogCocoa::InitDialogControls() {
152 main_container_.reset([[NSBox alloc] init]);
153 [main_container_ setBoxType:NSBoxCustom];
154 [main_container_ setBorderType:NSLineBorder];
155 [main_container_ setBorderWidth:0];
156 [main_container_ setCornerRadius:0];
157 [main_container_ setTitlePosition:NSNoTitle];
158 [main_container_ setBorderColor:[NSColor colorWithCalibratedRed:0.625
163 base::scoped_nsobject<NSScrollView> scroll_view(
164 [[NSScrollView alloc] initWithFrame:
165 NSMakeRect(0, 0, kCheckboxMaxWidth, kScrollAreaHeight)]);
166 [scroll_view setHasVerticalScroller:YES];
167 [scroll_view setHasHorizontalScroller:NO];
168 [scroll_view setBorderType:NSNoBorder];
169 [scroll_view setAutohidesScrollers:YES];
170 [[main_container_ contentView] addSubview:scroll_view];
172 // Add gallery permission checkboxes inside the scrolling view.
173 checkbox_container_.reset([[FlippedView alloc] initWithFrame:NSZeroRect]);
174 checkboxes_.reset([[NSMutableArray alloc] init]);
175 [scroll_view setDocumentView:checkbox_container_];
177 CGFloat y_pos = kCheckboxMargin;
179 y_pos = CreateAttachedCheckboxes(y_pos, controller_->AttachedPermissions());
181 if (!controller_->UnattachedPermissions().empty()) {
182 y_pos = CreateCheckboxSeparator(y_pos);
184 y_pos = CreateUnattachedCheckboxes(
185 y_pos, controller_->UnattachedPermissions());
188 [checkbox_container_ setFrame:NSMakeRect(0, 0, kCheckboxMaxWidth, y_pos + 2)];
190 // Resize to pack the scroll view if possible.
191 NSRect scroll_frame = [scroll_view frame];
192 if (NSHeight(scroll_frame) > NSHeight([checkbox_container_ frame])) {
193 scroll_frame.size.height = NSHeight([checkbox_container_ frame]);
194 [scroll_view setFrame:scroll_frame];
197 [main_container_ setFrame:NSMakeRect(
198 0, 0, kCheckboxMaxWidth, NSHeight(scroll_frame))];
199 [alert_ setAccessoryView:main_container_];
201 // As a safeguard against the user skipping reading over the dialog and just
202 // confirming, the button will be unavailable for dialogs without any checks
203 // until the user toggles something.
204 [[[alert_ buttons] objectAtIndex:0] setEnabled:
205 controller_->HasPermittedGalleries()];
210 CGFloat MediaGalleriesDialogCocoa::CreateAttachedCheckboxes(
212 const MediaGalleriesDialogController::GalleryPermissionsVector&
214 y_pos += kCheckboxMargin;
216 for (MediaGalleriesDialogController::GalleryPermissionsVector::
217 const_iterator iter = permissions.begin();
218 iter != permissions.end(); iter++) {
219 const MediaGalleriesDialogController::GalleryPermission& permission = *iter;
220 UpdateGalleryCheckbox(permission.pref_info, permission.allowed, y_pos);
221 y_pos = NSMaxY([[checkboxes_ lastObject] frame]) + kCheckboxMargin;
227 // Add checkboxes for galleries that aren't available (i.e. removable
228 // volumes that are not currently attached).
229 CGFloat MediaGalleriesDialogCocoa::CreateUnattachedCheckboxes(
231 const MediaGalleriesDialogController::GalleryPermissionsVector&
233 y_pos += kCheckboxMargin;
235 for (MediaGalleriesDialogController::GalleryPermissionsVector::
236 const_iterator iter = permissions.begin();
237 iter != permissions.end(); iter++) {
238 const MediaGalleriesDialogController::GalleryPermission& permission = *iter;
239 UpdateGalleryCheckbox(permission.pref_info, permission.allowed, y_pos);
240 y_pos = NSMaxY([[checkboxes_ lastObject] frame]) + kCheckboxMargin;
246 CGFloat MediaGalleriesDialogCocoa::CreateCheckboxSeparator(CGFloat y_pos) {
247 base::scoped_nsobject<NSBox> separator(
248 [[NSBox alloc] initWithFrame:NSMakeRect(
249 0, y_pos + kCheckboxMargin * 0.5, kCheckboxMaxWidth, 1.0)]);
250 [separator setBoxType:NSBoxSeparator];
251 [separator setBorderType:NSLineBorder];
252 [separator setAlphaValue:0.2];
253 [checkbox_container_ addSubview:separator];
254 y_pos += kCheckboxMargin * 0.5 + 4;
256 base::scoped_nsobject<NSTextField> unattached_label(
257 [[NSTextField alloc] initWithFrame:NSZeroRect]);
258 [unattached_label setEditable:NO];
259 [unattached_label setSelectable:NO];
260 [unattached_label setBezeled:NO];
261 [unattached_label setAttributedStringValue:
262 constrained_window::GetAttributedLabelString(
263 base::SysUTF16ToNSString(
264 controller_->GetUnattachedLocationsHeader()),
265 chrome_style::kTextFontStyle,
266 NSNaturalTextAlignment,
267 NSLineBreakByClipping
269 [unattached_label sizeToFit];
270 NSSize unattached_label_size = [unattached_label frame].size;
271 [unattached_label setFrame:NSMakeRect(
272 kCheckboxMargin, y_pos + kCheckboxMargin,
273 kCheckboxMaxWidth, unattached_label_size.height)];
274 [checkbox_container_ addSubview:unattached_label];
275 y_pos = NSMaxY([unattached_label frame]) + kCheckboxMargin;
280 void MediaGalleriesDialogCocoa::OnAcceptClicked() {
284 window_->CloseWebContentsModalDialog();
287 void MediaGalleriesDialogCocoa::OnCancelClicked() {
289 window_->CloseWebContentsModalDialog();
292 void MediaGalleriesDialogCocoa::OnAddFolderClicked() {
293 controller_->OnAddFolderClicked();
296 void MediaGalleriesDialogCocoa::OnCheckboxToggled(NSButton* checkbox) {
297 [[[alert_ buttons] objectAtIndex:0] setEnabled:YES];
299 const MediaGalleriesDialogController::GalleryPermissionsVector&
300 attached_permissions = controller_->AttachedPermissions();
301 for (MediaGalleriesDialogController::GalleryPermissionsVector::
302 const_reverse_iterator iter = attached_permissions.rbegin();
303 iter != attached_permissions.rend(); iter++) {
304 const MediaGalleryPrefInfo* gallery = &iter->pref_info;
305 NSString* unique_id = GetUniqueIDForGallery(*gallery);
306 if ([[[checkbox cell] representedObject] isEqual:unique_id]) {
307 controller_->DidToggleGalleryId(gallery->pref_id,
308 [checkbox state] == NSOnState);
313 const MediaGalleriesDialogController::GalleryPermissionsVector&
314 unattached_permissions = controller_->UnattachedPermissions();
315 for (MediaGalleriesDialogController::GalleryPermissionsVector::
316 const_reverse_iterator iter = unattached_permissions.rbegin();
317 iter != unattached_permissions.rend(); iter++) {
318 const MediaGalleryPrefInfo* gallery = &iter->pref_info;
319 NSString* unique_id = GetUniqueIDForGallery(*gallery);
320 if ([[[checkbox cell] representedObject] isEqual:unique_id]) {
321 controller_->DidToggleGalleryId(gallery->pref_id,
322 [checkbox state] == NSOnState);
329 void MediaGalleriesDialogCocoa::UpdateGalleryCheckbox(
330 const MediaGalleryPrefInfo& gallery,
334 base::scoped_nsobject<MediaGalleriesCheckbox> checkbox(
335 [[MediaGalleriesCheckbox alloc] initWithFrame:NSZeroRect
337 prefId:gallery.pref_id]);
338 NSString* unique_id = GetUniqueIDForGallery(gallery);
339 [[checkbox cell] setRepresentedObject:unique_id];
340 [[checkbox cell] setLineBreakMode:NSLineBreakByTruncatingMiddle];
341 [checkbox setButtonType:NSSwitchButton];
342 [checkbox setTarget:cocoa_controller_];
343 [checkbox setAction:@selector(onCheckboxToggled:)];
344 [checkboxes_ addObject:checkbox];
346 [checkbox setTitle:base::SysUTF16ToNSString(
347 gallery.GetGalleryDisplayName())];
348 [checkbox setToolTip:base::SysUTF16ToNSString(gallery.GetGalleryTooltip())];
349 [checkbox setState:permitted ? NSOnState : NSOffState];
351 [checkbox sizeToFit];
352 NSRect rect = [checkbox bounds];
355 base::scoped_nsobject<NSTextField> details(
356 [[NSTextField alloc] initWithFrame:NSZeroRect]);
357 [details setEditable:NO];
358 [details setSelectable:NO];
359 [details setBezeled:NO];
360 [details setAttributedStringValue:
361 constrained_window::GetAttributedLabelString(
362 base::SysUTF16ToNSString(gallery.GetGalleryAdditionalDetails()),
363 chrome_style::kTextFontStyle,
364 NSNaturalTextAlignment,
365 NSLineBreakByClipping
367 [details setTextColor:[NSColor colorWithCalibratedRed:0.625
372 NSRect details_rect = [details bounds];
374 // The checkbox will elide so reduce its size so it will all fit.
376 std::min(NSWidth(rect),
377 kCheckboxMaxWidth - 3 * kCheckboxMargin - NSWidth(details_rect));
378 rect.origin = NSMakePoint(kCheckboxMargin, y_pos);
379 [checkbox setFrame:rect];
380 [checkbox_container_ addSubview:checkbox];
382 details_rect.origin = NSMakePoint(NSMaxX(rect) + kCheckboxMargin, y_pos - 1);
383 [details setFrame:details_rect];
385 [checkbox_container_ addSubview:details];
388 void MediaGalleriesDialogCocoa::UpdateGalleries() {
389 InitDialogControls();
392 void MediaGalleriesDialogCocoa::OnConstrainedWindowClosed(
393 ConstrainedWindowMac* window) {
394 controller_->DialogFinished(accepted_);
397 ui::MenuModel* MediaGalleriesDialogCocoa::GetContextMenu(
398 MediaGalleryPrefId prefid) {
399 return controller_->GetContextMenu(prefid);
403 MediaGalleriesDialog* MediaGalleriesDialog::Create(
404 MediaGalleriesDialogController* controller) {
405 base::scoped_nsobject<MediaGalleriesCocoaController> cocoa_controller(
406 [[MediaGalleriesCocoaController alloc] init]);
407 return new MediaGalleriesDialogCocoa(controller, cocoa_controller);