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 #include "base/command_line.h"
9 #import "base/mac/bundle_locations.h"
10 #include "base/strings/sys_string_conversions.h"
11 #import "chrome/browser/ui/cocoa/media_picker/desktop_media_picker_item.h"
12 #include "chrome/common/chrome_switches.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "content/public/browser/browser_thread.h"
15 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
16 #import "ui/base/cocoa/flipped_view.h"
17 #import "ui/base/cocoa/window_size_constants.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/gfx/image/image_skia_util_mac.h"
23 const int kInitialContentWidth = 620;
24 const int kMinimumContentWidth = 500;
25 const int kMinimumContentHeight = 390;
26 const int kThumbnailWidth = 150;
27 const int kThumbnailHeight = 150;
28 const int kFramePadding = 20;
29 const int kControlSpacing = 10;
30 const int kExcessButtonPadding = 6;
34 @interface DesktopMediaPickerController (Private)
36 // Populate the window with controls and views.
37 - (void)initializeContentsWithAppName:(const base::string16&)appName;
39 // Create a |NSTextField| with label traits given |width|. Frame height is
40 // automatically adjusted to fit.
41 - (NSTextField*)createTextFieldWithText:(NSString*)text
42 frameWidth:(CGFloat)width;
44 // Create a button with |title|, with size adjusted to fit.
45 - (NSButton*)createButtonWithTitle:(NSString*)title;
47 // Report result by invoking |doneCallback_|. The callback is invoked only on
48 // the first call to |reportResult:|. Subsequent calls will be no-ops.
49 - (void)reportResult:(content::DesktopMediaID)sourceID;
52 - (void)sharePressed:(id)sender;
53 - (void)cancelPressed:(id)sender;
57 @implementation DesktopMediaPickerController
59 - (id)initWithMediaList:(scoped_ptr<DesktopMediaList>)media_list
60 parent:(NSWindow*)parent
61 callback:(const DesktopMediaPicker::DoneCallback&)callback
62 appName:(const base::string16&)appName
63 targetName:(const base::string16&)targetName {
64 const NSUInteger kStyleMask =
65 NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask;
66 base::scoped_nsobject<NSWindow> window(
67 [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
69 backing:NSBackingStoreBuffered
72 if ((self = [super initWithWindow:window])) {
73 [parent addChildWindow:window ordered:NSWindowAbove];
74 [window setDelegate:self];
75 [self initializeContentsWithAppName:appName targetName:targetName];
76 media_list_ = media_list.Pass();
77 media_list_->SetViewDialogWindowId([window windowNumber]);
78 doneCallback_ = callback;
79 items_.reset([[NSMutableArray alloc] init]);
80 bridge_.reset(new DesktopMediaPickerBridge(self));
86 [shareButton_ setTarget:nil];
87 [cancelButton_ setTarget:nil];
88 [sourceBrowser_ setDelegate:nil];
89 [sourceBrowser_ setDataSource:nil];
90 [[self window] close];
94 - (void)initializeContentsWithAppName:(const base::string16&)appName
95 targetName:(const base::string16&)targetName {
96 // Use flipped coordinates to facilitate manual layout.
97 const CGFloat kPaddedWidth = kInitialContentWidth - (kFramePadding * 2);
98 base::scoped_nsobject<FlippedView> content(
99 [[FlippedView alloc] initWithFrame:NSZeroRect]);
100 [[self window] setContentView:content];
101 NSPoint origin = NSMakePoint(kFramePadding, kFramePadding);
103 // Set the dialog's title.
104 NSString* titleText = l10n_util::GetNSStringF(
105 IDS_DESKTOP_MEDIA_PICKER_TITLE, appName);
106 [[self window] setTitle:titleText];
108 // Set the dialog's description.
109 NSString* descriptionText;
110 if (appName == targetName) {
111 descriptionText = l10n_util::GetNSStringF(
112 IDS_DESKTOP_MEDIA_PICKER_TEXT, appName);
114 descriptionText = l10n_util::GetNSStringF(
115 IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, appName, targetName);
117 NSTextField* description = [self createTextFieldWithText:descriptionText
118 frameWidth:kPaddedWidth];
119 [description setFrameOrigin:origin];
120 [content addSubview:description];
121 origin.y += NSHeight([description frame]) + kControlSpacing;
123 // Create the image browser.
124 sourceBrowser_.reset([[IKImageBrowserView alloc] initWithFrame:NSZeroRect]);
125 NSUInteger cellStyle = IKCellsStyleShadowed | IKCellsStyleTitled;
126 [sourceBrowser_ setDelegate:self];
127 [sourceBrowser_ setDataSource:self];
128 [sourceBrowser_ setCellsStyleMask:cellStyle];
129 [sourceBrowser_ setCellSize:NSMakeSize(kThumbnailWidth, kThumbnailHeight)];
130 [sourceBrowser_ setAllowsMultipleSelection:NO];
132 // Create a scroll view to host the image browser.
133 NSRect imageBrowserScrollFrame = NSMakeRect(
134 origin.x, origin.y, kPaddedWidth, 350);
135 base::scoped_nsobject<NSScrollView> imageBrowserScroll(
136 [[NSScrollView alloc] initWithFrame:imageBrowserScrollFrame]);
137 [imageBrowserScroll setHasVerticalScroller:YES];
138 [imageBrowserScroll setDocumentView:sourceBrowser_];
139 [imageBrowserScroll setBorderType:NSBezelBorder];
140 [imageBrowserScroll setAutoresizingMask:
141 NSViewWidthSizable | NSViewHeightSizable];
142 [content addSubview:imageBrowserScroll];
143 origin.y += NSHeight(imageBrowserScrollFrame) + kControlSpacing;
145 // Create the share button.
146 shareButton_ = [self createButtonWithTitle:l10n_util::GetNSString(
147 IDS_DESKTOP_MEDIA_PICKER_SHARE)];
148 origin.x = kInitialContentWidth - kFramePadding -
149 (NSWidth([shareButton_ frame]) - kExcessButtonPadding);
150 [shareButton_ setEnabled:NO];
151 [shareButton_ setFrameOrigin:origin];
152 [shareButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
153 [shareButton_ setTarget:self];
154 [shareButton_ setAction:@selector(sharePressed:)];
155 [content addSubview:shareButton_];
157 // Create the cancel button.
159 [self createButtonWithTitle:l10n_util::GetNSString(IDS_CANCEL)];
160 origin.x -= kControlSpacing +
161 (NSWidth([cancelButton_ frame]) - (kExcessButtonPadding * 2));
162 [cancelButton_ setFrameOrigin:origin];
163 [cancelButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
164 [cancelButton_ setTarget:self];
165 [cancelButton_ setAction:@selector(cancelPressed:)];
166 [content addSubview:cancelButton_];
167 origin.y += kFramePadding +
168 (NSHeight([cancelButton_ frame]) - kExcessButtonPadding);
170 // Resize window to fit.
171 [[[self window] contentView] setAutoresizesSubviews:NO];
172 [[self window] setContentSize:NSMakeSize(kInitialContentWidth, origin.y)];
173 [[self window] setContentMinSize:
174 NSMakeSize(kMinimumContentWidth, kMinimumContentHeight)];
175 [[[self window] contentView] setAutoresizesSubviews:YES];
178 - (void)showWindow:(id)sender {
179 // Signal the media_list to start sending thumbnails. |bridge_| is used as the
180 // observer, and will forward notifications to this object.
181 media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
182 media_list_->StartUpdating(bridge_.get());
184 [self.window center];
185 [super showWindow:sender];
188 - (void)reportResult:(content::DesktopMediaID)sourceID {
189 if (doneCallback_.is_null()) {
193 // Notify the |callback_| asynchronously because it may release the
195 content::BrowserThread::PostTask(
196 content::BrowserThread::UI, FROM_HERE,
197 base::Bind(doneCallback_, sourceID));
198 doneCallback_.Reset();
201 - (void)sharePressed:(id)sender {
202 NSIndexSet* indexes = [sourceBrowser_ selectionIndexes];
203 NSUInteger selectedIndex = [indexes firstIndex];
204 DesktopMediaPickerItem* item =
205 [items_ objectAtIndex:selectedIndex];
206 [self reportResult:[item sourceID]];
210 - (void)cancelPressed:(id)sender {
211 [self reportResult:content::DesktopMediaID()];
215 - (NSTextField*)createTextFieldWithText:(NSString*)text
216 frameWidth:(CGFloat)width {
217 NSRect frame = NSMakeRect(0, 0, width, 1);
218 base::scoped_nsobject<NSTextField> textField(
219 [[NSTextField alloc] initWithFrame:frame]);
220 [textField setEditable:NO];
221 [textField setSelectable:YES];
222 [textField setDrawsBackground:NO];
223 [textField setBezeled:NO];
224 [textField setStringValue:text];
225 [textField setFont:[NSFont systemFontOfSize:13]];
226 [textField setAutoresizingMask:NSViewWidthSizable];
227 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:textField];
228 return textField.autorelease();
231 - (NSButton*)createButtonWithTitle:(NSString*)title {
232 base::scoped_nsobject<NSButton> button(
233 [[NSButton alloc] initWithFrame:NSZeroRect]);
234 [button setButtonType:NSMomentaryPushInButton];
235 [button setBezelStyle:NSRoundedBezelStyle];
236 [button setTitle:title];
237 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
238 return button.autorelease();
241 #pragma mark NSWindowDelegate
243 - (void)windowWillClose:(NSNotification*)notification {
244 // Report the result if it hasn't been reported yet. |reportResult:| ensures
245 // that the result is only reported once.
246 [self reportResult:content::DesktopMediaID()];
248 // Remove self from the parent.
249 NSWindow* window = [self window];
250 [[window parentWindow] removeChildWindow:window];
253 #pragma mark IKImageBrowserDataSource
255 - (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView*)browser {
256 return [items_ count];
259 - (id)imageBrowser:(IKImageBrowserView *)browser
260 itemAtIndex:(NSUInteger)index {
261 return [items_ objectAtIndex:index];
264 #pragma mark IKImageBrowserDelegate
266 - (void)imageBrowser:(IKImageBrowserView *)browser
267 cellWasDoubleClickedAtIndex:(NSUInteger)index {
268 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
269 [self reportResult:[item sourceID]];
273 - (void)imageBrowserSelectionDidChange:(IKImageBrowserView*) aBrowser {
274 // Enable or disable the OK button based on whether we have a selection.
275 [shareButton_ setEnabled:([[sourceBrowser_ selectionIndexes] count] > 0)];
278 #pragma mark DesktopMediaPickerObserver
280 - (void)sourceAddedAtIndex:(int)index {
281 const DesktopMediaList::Source& source = media_list_->GetSource(index);
282 NSString* imageTitle = base::SysUTF16ToNSString(source.name);
283 base::scoped_nsobject<DesktopMediaPickerItem> item(
284 [[DesktopMediaPickerItem alloc] initWithSourceId:source.id
285 imageUID:++lastImageUID_
286 imageTitle:imageTitle]);
287 [items_ insertObject:item atIndex:index];
288 [sourceBrowser_ reloadData];
290 NSString* autoselectSource = base::SysUTF8ToNSString(
291 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
292 switches::kAutoSelectDesktopCaptureSource));
294 if ([autoselectSource isEqualToString:imageTitle]) {
295 [self reportResult:[item sourceID]];
300 - (void)sourceRemovedAtIndex:(int)index {
301 if ([[sourceBrowser_ selectionIndexes] containsIndex:index]) {
302 // Selected item was removed. Clear selection.
303 [sourceBrowser_ setSelectionIndexes:[NSIndexSet indexSet]
304 byExtendingSelection:FALSE];
306 [items_ removeObjectAtIndex:index];
307 [sourceBrowser_ reloadData];
310 - (void)sourceMovedFrom:(int)oldIndex to:(int)newIndex {
311 base::scoped_nsobject<DesktopMediaPickerItem> item(
312 [[items_ objectAtIndex:oldIndex] retain]);
313 [items_ removeObjectAtIndex:oldIndex];
314 [items_ insertObject:item atIndex:newIndex];
315 [sourceBrowser_ reloadData];
318 - (void)sourceNameChangedAtIndex:(int)index {
319 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
320 const DesktopMediaList::Source& source = media_list_->GetSource(index);
321 [item setImageTitle:base::SysUTF16ToNSString(source.name)];
322 [sourceBrowser_ reloadData];
325 - (void)sourceThumbnailChangedAtIndex:(int)index {
326 const DesktopMediaList::Source& source = media_list_->GetSource(index);
327 NSImage* image = gfx::NSImageFromImageSkia(source.thumbnail);
329 DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
330 [item setImageRepresentation:image];
331 [sourceBrowser_ reloadData];
334 @end // @interface DesktopMediaPickerController