1 // Copyright 2013 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/media_picker/desktop_media_picker_controller.h"
8 #import "base/mac/bundle_locations.h"
9 #include "base/strings/sys_string_conversions.h"
10 #import "chrome/browser/ui/cocoa/media_picker/desktop_media_picker_item.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "grit/generated_resources.h"
13 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
14 #import "ui/base/cocoa/flipped_view.h"
15 #import "ui/base/cocoa/window_size_constants.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/gfx/image/image_skia_util_mac.h"
21 const int kInitialContentWidth = 620;
22 const int kMinimumContentWidth = 500;
23 const int kMinimumContentHeight = 390;
24 const int kThumbnailWidth = 150;
25 const int kThumbnailHeight = 150;
26 const int kFramePadding = 20;
27 const int kControlSpacing = 10;
28 const int kExcessButtonPadding = 6;
32 @interface DesktopMediaPickerController (Private)
34 // Populate the window with controls and views.
35 - (void)initializeContentsWithAppName:(const base::string16&)appName;
37 // Create a |NSTextField| with label traits given |width|. Frame height is
38 // automatically adjusted to fit.
39 - (NSTextField*)createTextFieldWithText:(NSString*)text
40 frameWidth:(CGFloat)width;
42 // Create a button with |title|, with size adjusted to fit.
43 - (NSButton*)createButtonWithTitle:(NSString*)title;
45 // Report result by invoking |doneCallback_|. The callback is invoked only on
46 // the first call to |reportResult:|. Subsequent calls will be no-ops.
47 - (void)reportResult:(content::DesktopMediaID)sourceID;
50 - (void)sharePressed:(id)sender;
51 - (void)cancelPressed:(id)sender;
55 @implementation DesktopMediaPickerController
57 - (id)initWithMediaList:(scoped_ptr<DesktopMediaList>)media_list
58 parent:(NSWindow*)parent
59 callback:(const DesktopMediaPicker::DoneCallback&)callback
60 appName:(const base::string16&)appName
61 targetName:(const base::string16&)targetName {
62 const NSUInteger kStyleMask =
63 NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask;
64 base::scoped_nsobject<NSWindow> window(
65 [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
67 backing:NSBackingStoreBuffered
70 if ((self = [super initWithWindow:window])) {
71 [parent addChildWindow:window ordered:NSWindowAbove];
72 [window setDelegate:self];
73 [self initializeContentsWithAppName:appName targetName:targetName];
74 media_list_ = media_list.Pass();
75 media_list_->SetViewDialogWindowId([window windowNumber]);
76 doneCallback_ = callback;
77 items_.reset([[NSMutableArray alloc] init]);
78 bridge_.reset(new DesktopMediaPickerBridge(self));
84 [shareButton_ setTarget:nil];
85 [cancelButton_ setTarget:nil];
86 [sourceBrowser_ setDelegate:nil];
87 [sourceBrowser_ setDataSource:nil];
91 - (void)initializeContentsWithAppName:(const base::string16&)appName
92 targetName:(const base::string16&)targetName {
93 // Use flipped coordinates to facilitate manual layout.
94 const CGFloat kPaddedWidth = kInitialContentWidth - (kFramePadding * 2);
95 base::scoped_nsobject<FlippedView> content(
96 [[FlippedView alloc] initWithFrame:NSZeroRect]);
97 [[self window] setContentView:content];
98 NSPoint origin = NSMakePoint(kFramePadding, kFramePadding);
100 // Set the dialog's title.
101 NSString* titleText = l10n_util::GetNSStringF(
102 IDS_DESKTOP_MEDIA_PICKER_TITLE, appName);
103 [[self window] setTitle:titleText];
105 // Set the dialog's description.
106 NSString* descriptionText;
107 if (appName == targetName) {
108 descriptionText = l10n_util::GetNSStringF(
109 IDS_DESKTOP_MEDIA_PICKER_TEXT, appName);
111 descriptionText = l10n_util::GetNSStringF(
112 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, appName, targetName);
114 NSTextField* description = [self createTextFieldWithText:descriptionText
115 frameWidth:kPaddedWidth];
116 [description setFrameOrigin:origin];
117 [content addSubview:description];
118 origin.y += NSHeight([description frame]) + kControlSpacing;
120 // Create the image browser.
121 sourceBrowser_.reset([[IKImageBrowserView alloc] initWithFrame:NSZeroRect]);
122 NSUInteger cellStyle = IKCellsStyleShadowed | IKCellsStyleTitled;
123 [sourceBrowser_ setDelegate:self];
124 [sourceBrowser_ setDataSource:self];
125 [sourceBrowser_ setCellsStyleMask:cellStyle];
126 [sourceBrowser_ setCellSize:NSMakeSize(kThumbnailWidth, kThumbnailHeight)];
128 // Create a scroll view to host the image browser.
129 NSRect imageBrowserScrollFrame = NSMakeRect(
130 origin.x, origin.y, kPaddedWidth, 350);
131 base::scoped_nsobject<NSScrollView> imageBrowserScroll(
132 [[NSScrollView alloc] initWithFrame:imageBrowserScrollFrame]);
133 [imageBrowserScroll setHasVerticalScroller:YES];
134 [imageBrowserScroll setDocumentView:sourceBrowser_];
135 [imageBrowserScroll setBorderType:NSBezelBorder];
136 [imageBrowserScroll setAutoresizingMask:
137 NSViewWidthSizable | NSViewHeightSizable];
138 [content addSubview:imageBrowserScroll];
139 origin.y += NSHeight(imageBrowserScrollFrame) + kControlSpacing;
141 // Create the share button.
142 shareButton_ = [self createButtonWithTitle:l10n_util::GetNSString(
143 IDS_DESKTOP_MEDIA_PICKER_SHARE)];
144 origin.x = kInitialContentWidth - kFramePadding -
145 (NSWidth([shareButton_ frame]) - kExcessButtonPadding);
146 [shareButton_ setEnabled:NO];
147 [shareButton_ setFrameOrigin:origin];
148 [shareButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
149 [shareButton_ setTarget:self];
150 [shareButton_ setAction:@selector(sharePressed:)];
151 [content addSubview:shareButton_];
153 // Create the cancel button.
155 [self createButtonWithTitle:l10n_util::GetNSString(IDS_CANCEL)];
156 origin.x -= kControlSpacing +
157 (NSWidth([cancelButton_ frame]) - (kExcessButtonPadding * 2));
158 [cancelButton_ setFrameOrigin:origin];
159 [cancelButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
160 [cancelButton_ setTarget:self];
161 [cancelButton_ setAction:@selector(cancelPressed:)];
162 [content addSubview:cancelButton_];
163 origin.y += kFramePadding +
164 (NSHeight([cancelButton_ frame]) - kExcessButtonPadding);
166 // Resize window to fit.
167 [[[self window] contentView] setAutoresizesSubviews:NO];
168 [[self window] setContentSize:NSMakeSize(kInitialContentWidth, origin.y)];
169 [[self window] setContentMinSize:
170 NSMakeSize(kMinimumContentWidth, kMinimumContentHeight)];
171 [[[self window] contentView] setAutoresizesSubviews:YES];
174 - (void)showWindow:(id)sender {
175 // Signal the media_list to start sending thumbnails. |bridge_| is used as the
176 // observer, and will forward notifications to this object.
177 media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
178 media_list_->StartUpdating(bridge_.get());
180 [self.window center];
181 [super showWindow:sender];
184 - (void)reportResult:(content::DesktopMediaID)sourceID {
185 if (doneCallback_.is_null()) {
189 // Notify the |callback_| asynchronously because it may release the
191 content::BrowserThread::PostTask(
192 content::BrowserThread::UI, FROM_HERE,
193 base::Bind(doneCallback_, sourceID));
194 doneCallback_.Reset();
197 - (void)sharePressed:(id)sender {
198 NSIndexSet* indexes = [sourceBrowser_ selectionIndexes];
199 NSUInteger selectedIndex = [indexes firstIndex];
200 DesktopMediaPickerItem* item =
201 [items_ objectAtIndex:selectedIndex];
202 [self reportResult:[item sourceID]];
206 - (void)cancelPressed:(id)sender {
207 [self reportResult:content::DesktopMediaID()];
211 - (NSTextField*)createTextFieldWithText:(NSString*)text
212 frameWidth:(CGFloat)width {
213 NSRect frame = NSMakeRect(0, 0, width, 1);
214 base::scoped_nsobject<NSTextField> textField(
215 [[NSTextField alloc] initWithFrame:frame]);
216 [textField setEditable:NO];
217 [textField setSelectable:YES];
218 [textField setDrawsBackground:NO];
219 [textField setBezeled:NO];
220 [textField setStringValue:text];
221 [textField setFont:[NSFont systemFontOfSize:13]];
222 [textField setAutoresizingMask:NSViewWidthSizable];
223 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:textField];
224 return textField.autorelease();
227 - (NSButton*)createButtonWithTitle:(NSString*)title {
228 base::scoped_nsobject<NSButton> button(
229 [[NSButton alloc] initWithFrame:NSZeroRect]);
230 [button setButtonType:NSMomentaryPushInButton];
231 [button setBezelStyle:NSRoundedBezelStyle];
232 [button setTitle:title];
233 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
234 return button.autorelease();
237 #pragma mark NSWindowDelegate
239 - (void)windowWillClose:(NSNotification*)notification {
240 // Report the result if it hasn't been reported yet. |reportResult:| ensures
241 // that the result is only reported once.
242 [self reportResult:content::DesktopMediaID()];
244 // Remove self from the parent.
245 NSWindow* window = [self window];
246 [[window parentWindow] removeChildWindow:window];
249 #pragma mark IKImageBrowserDataSource
251 - (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView*)browser {
252 return [items_ count];
255 - (id)imageBrowser:(IKImageBrowserView *)browser
256 itemAtIndex:(NSUInteger)index {
257 return [items_ objectAtIndex:index];
260 #pragma mark IKImageBrowserDelegate
262 - (void)imageBrowser:(IKImageBrowserView *)browser
263 cellWasDoubleClickedAtIndex:(NSUInteger)index {
264 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
265 [self reportResult:[item sourceID]];
269 - (void)imageBrowserSelectionDidChange:(IKImageBrowserView*) aBrowser {
270 // Enable or disable the OK button based on whether we have a selection.
271 [shareButton_ setEnabled:([[sourceBrowser_ selectionIndexes] count] > 0)];
274 #pragma mark DesktopMediaPickerObserver
276 - (void)sourceAddedAtIndex:(int)index {
277 const DesktopMediaList::Source& source = media_list_->GetSource(index);
278 NSString* imageTitle = base::SysUTF16ToNSString(source.name);
279 base::scoped_nsobject<DesktopMediaPickerItem> item(
280 [[DesktopMediaPickerItem alloc] initWithSourceId:source.id
281 imageUID:++lastImageUID_
282 imageTitle:imageTitle]);
283 [items_ insertObject:item atIndex:index];
284 [sourceBrowser_ reloadData];
287 - (void)sourceRemovedAtIndex:(int)index {
288 if ([[sourceBrowser_ selectionIndexes] containsIndex:index]) {
289 // Selected item was removed. Clear selection.
290 [sourceBrowser_ setSelectionIndexes:[NSIndexSet indexSet]
291 byExtendingSelection:FALSE];
293 [items_ removeObjectAtIndex:index];
294 [sourceBrowser_ reloadData];
297 - (void)sourceMovedFrom:(int)oldIndex to:(int)newIndex {
298 base::scoped_nsobject<DesktopMediaPickerItem> item(
299 [[items_ objectAtIndex:oldIndex] retain]);
300 [items_ removeObjectAtIndex:oldIndex];
301 [items_ insertObject:item atIndex:newIndex];
302 [sourceBrowser_ reloadData];
305 - (void)sourceNameChangedAtIndex:(int)index {
306 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
307 const DesktopMediaList::Source& source = media_list_->GetSource(index);
308 [item setImageTitle:base::SysUTF16ToNSString(source.name)];
309 [sourceBrowser_ reloadData];
312 - (void)sourceThumbnailChangedAtIndex:(int)index {
313 const DesktopMediaList::Source& source = media_list_->GetSource(index);
314 NSImage* image = gfx::NSImageFromImageSkia(source.thumbnail);
316 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
317 [item setImageRepresentation:image];
318 [sourceBrowser_ reloadData];
321 @end // @interface DesktopMediaPickerController