Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / media_picker / desktop_media_picker_controller.mm
blob18708441cb76e730c3de539cd8160d8e0b034127
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"
7 #include "base/bind.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"
21 namespace {
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;
32 }  // namespace
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;
51 // Action handlers.
52 - (void)sharePressed:(id)sender;
53 - (void)cancelPressed:(id)sender;
55 @end
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
68                                   styleMask:kStyleMask
69                                     backing:NSBackingStoreBuffered
70                                       defer:NO]);
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(content::DesktopMediaID(
78        content::DesktopMediaID::TYPE_WINDOW, [window windowNumber]));
79     doneCallback_ = callback;
80     items_.reset([[NSMutableArray alloc] init]);
81     bridge_.reset(new DesktopMediaPickerBridge(self));
82   }
83   return self;
86 - (void)dealloc {
87   [shareButton_ setTarget:nil];
88   [cancelButton_ setTarget:nil];
89   [sourceBrowser_ setDelegate:nil];
90   [sourceBrowser_ setDataSource:nil];
91   [[self window] close];
92   [super dealloc];
95 - (void)initializeContentsWithAppName:(const base::string16&)appName
96                            targetName:(const base::string16&)targetName {
97   // Use flipped coordinates to facilitate manual layout.
98   const CGFloat kPaddedWidth = kInitialContentWidth - (kFramePadding * 2);
99   base::scoped_nsobject<FlippedView> content(
100       [[FlippedView alloc] initWithFrame:NSZeroRect]);
101   [[self window] setContentView:content];
102   NSPoint origin = NSMakePoint(kFramePadding, kFramePadding);
104   // Set the dialog's title.
105   NSString* titleText = l10n_util::GetNSStringF(
106       IDS_DESKTOP_MEDIA_PICKER_TITLE, appName);
107   [[self window] setTitle:titleText];
109   // Set the dialog's description.
110   NSString* descriptionText;
111   if (appName == targetName) {
112     descriptionText = l10n_util::GetNSStringF(
113         IDS_DESKTOP_MEDIA_PICKER_TEXT, appName);
114   } else {
115     descriptionText = l10n_util::GetNSStringF(
116         IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, appName, targetName);
117   }
118   NSTextField* description = [self createTextFieldWithText:descriptionText
119                                                 frameWidth:kPaddedWidth];
120   [description setFrameOrigin:origin];
121   [content addSubview:description];
122   origin.y += NSHeight([description frame]) + kControlSpacing;
124   // Create the image browser.
125   sourceBrowser_.reset([[IKImageBrowserView alloc] initWithFrame:NSZeroRect]);
126   NSUInteger cellStyle = IKCellsStyleShadowed | IKCellsStyleTitled;
127   [sourceBrowser_ setDelegate:self];
128   [sourceBrowser_ setDataSource:self];
129   [sourceBrowser_ setCellsStyleMask:cellStyle];
130   [sourceBrowser_ setCellSize:NSMakeSize(kThumbnailWidth, kThumbnailHeight)];
131   [sourceBrowser_ setAllowsMultipleSelection:NO];
133   // Create a scroll view to host the image browser.
134   NSRect imageBrowserScrollFrame = NSMakeRect(
135       origin.x, origin.y, kPaddedWidth, 350);
136   base::scoped_nsobject<NSScrollView> imageBrowserScroll(
137       [[NSScrollView alloc] initWithFrame:imageBrowserScrollFrame]);
138   [imageBrowserScroll setHasVerticalScroller:YES];
139   [imageBrowserScroll setDocumentView:sourceBrowser_];
140   [imageBrowserScroll setBorderType:NSBezelBorder];
141   [imageBrowserScroll setAutoresizingMask:
142       NSViewWidthSizable | NSViewHeightSizable];
143   [content addSubview:imageBrowserScroll];
144   origin.y += NSHeight(imageBrowserScrollFrame) + kControlSpacing;
146   // Create the share button.
147   shareButton_ = [self createButtonWithTitle:l10n_util::GetNSString(
148       IDS_DESKTOP_MEDIA_PICKER_SHARE)];
149   origin.x = kInitialContentWidth - kFramePadding -
150       (NSWidth([shareButton_ frame]) - kExcessButtonPadding);
151   [shareButton_ setEnabled:NO];
152   [shareButton_ setFrameOrigin:origin];
153   [shareButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
154   [shareButton_ setTarget:self];
155   [shareButton_ setAction:@selector(sharePressed:)];
156   [content addSubview:shareButton_];
158   // Create the cancel button.
159   cancelButton_ =
160       [self createButtonWithTitle:l10n_util::GetNSString(IDS_CANCEL)];
161   origin.x -= kControlSpacing +
162       (NSWidth([cancelButton_ frame]) - (kExcessButtonPadding * 2));
163   [cancelButton_ setFrameOrigin:origin];
164   [cancelButton_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
165   [cancelButton_ setTarget:self];
166   [cancelButton_ setAction:@selector(cancelPressed:)];
167   [content addSubview:cancelButton_];
168   origin.y += kFramePadding +
169       (NSHeight([cancelButton_ frame]) - kExcessButtonPadding);
171   // Resize window to fit.
172   [[[self window] contentView] setAutoresizesSubviews:NO];
173   [[self window] setContentSize:NSMakeSize(kInitialContentWidth, origin.y)];
174   [[self window] setContentMinSize:
175       NSMakeSize(kMinimumContentWidth, kMinimumContentHeight)];
176   [[[self window] contentView] setAutoresizesSubviews:YES];
179 - (void)showWindow:(id)sender {
180   // Signal the media_list to start sending thumbnails. |bridge_| is used as the
181   // observer, and will forward notifications to this object.
182   media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight));
183   media_list_->StartUpdating(bridge_.get());
185   [self.window center];
186   [super showWindow:sender];
189 - (void)reportResult:(content::DesktopMediaID)sourceID {
190   if (doneCallback_.is_null()) {
191     return;
192   }
194   // Notify the |callback_| asynchronously because it may release the
195   // controller.
196   content::BrowserThread::PostTask(
197       content::BrowserThread::UI, FROM_HERE,
198       base::Bind(doneCallback_, sourceID));
199   doneCallback_.Reset();
202 - (void)sharePressed:(id)sender {
203   NSIndexSet* indexes = [sourceBrowser_ selectionIndexes];
204   NSUInteger selectedIndex = [indexes firstIndex];
205   DesktopMediaPickerItem* item =
206       [items_ objectAtIndex:selectedIndex];
207   [self reportResult:[item sourceID]];
208   [self close];
211 - (void)cancelPressed:(id)sender {
212   [self reportResult:content::DesktopMediaID()];
213   [self close];
216 - (NSTextField*)createTextFieldWithText:(NSString*)text
217                              frameWidth:(CGFloat)width {
218   NSRect frame = NSMakeRect(0, 0, width, 1);
219   base::scoped_nsobject<NSTextField> textField(
220       [[NSTextField alloc] initWithFrame:frame]);
221   [textField setEditable:NO];
222   [textField setSelectable:YES];
223   [textField setDrawsBackground:NO];
224   [textField setBezeled:NO];
225   [textField setStringValue:text];
226   [textField setFont:[NSFont systemFontOfSize:13]];
227   [textField setAutoresizingMask:NSViewWidthSizable];
228   [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:textField];
229   return textField.autorelease();
232 - (NSButton*)createButtonWithTitle:(NSString*)title {
233   base::scoped_nsobject<NSButton> button(
234       [[NSButton alloc] initWithFrame:NSZeroRect]);
235   [button setButtonType:NSMomentaryPushInButton];
236   [button setBezelStyle:NSRoundedBezelStyle];
237   [button setTitle:title];
238   [GTMUILocalizerAndLayoutTweaker sizeToFitView:button];
239   return button.autorelease();
242 #pragma mark NSWindowDelegate
244 - (void)windowWillClose:(NSNotification*)notification {
245   // Report the result if it hasn't been reported yet. |reportResult:| ensures
246   // that the result is only reported once.
247   [self reportResult:content::DesktopMediaID()];
249   // Remove self from the parent.
250   NSWindow* window = [self window];
251   [[window parentWindow] removeChildWindow:window];
254 #pragma mark IKImageBrowserDataSource
256 - (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView*)browser {
257   return [items_ count];
260 - (id)imageBrowser:(IKImageBrowserView *)browser
261        itemAtIndex:(NSUInteger)index {
262   return [items_ objectAtIndex:index];
265 #pragma mark IKImageBrowserDelegate
267 - (void)imageBrowser:(IKImageBrowserView *)browser
268       cellWasDoubleClickedAtIndex:(NSUInteger)index {
269   DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
270   [self reportResult:[item sourceID]];
271   [self close];
274 - (void)imageBrowserSelectionDidChange:(IKImageBrowserView*) aBrowser {
275   // Enable or disable the OK button based on whether we have a selection.
276   [shareButton_ setEnabled:([[sourceBrowser_ selectionIndexes] count] > 0)];
279 #pragma mark DesktopMediaPickerObserver
281 - (void)sourceAddedAtIndex:(int)index {
282   const DesktopMediaList::Source& source = media_list_->GetSource(index);
283   NSString* imageTitle = base::SysUTF16ToNSString(source.name);
284   base::scoped_nsobject<DesktopMediaPickerItem> item(
285       [[DesktopMediaPickerItem alloc] initWithSourceId:source.id
286                                               imageUID:++lastImageUID_
287                                             imageTitle:imageTitle]);
288   [items_ insertObject:item atIndex:index];
289   [sourceBrowser_ reloadData];
291   NSString* autoselectSource = base::SysUTF8ToNSString(
292       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
293           switches::kAutoSelectDesktopCaptureSource));
295   if ([autoselectSource isEqualToString:imageTitle]) {
296     [self reportResult:[item sourceID]];
297     [self close];
298   }
301 - (void)sourceRemovedAtIndex:(int)index {
302   if ([[sourceBrowser_ selectionIndexes] containsIndex:index]) {
303     // Selected item was removed. Clear selection.
304     [sourceBrowser_ setSelectionIndexes:[NSIndexSet indexSet]
305                       byExtendingSelection:FALSE];
306   }
307   [items_ removeObjectAtIndex:index];
308   [sourceBrowser_ reloadData];
311 - (void)sourceMovedFrom:(int)oldIndex to:(int)newIndex {
312   base::scoped_nsobject<DesktopMediaPickerItem> item(
313       [[items_ objectAtIndex:oldIndex] retain]);
314   [items_ removeObjectAtIndex:oldIndex];
315   [items_ insertObject:item atIndex:newIndex];
316   [sourceBrowser_ reloadData];
319 - (void)sourceNameChangedAtIndex:(int)index {
320   DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
321   const DesktopMediaList::Source& source = media_list_->GetSource(index);
322   [item setImageTitle:base::SysUTF16ToNSString(source.name)];
323   [sourceBrowser_ reloadData];
326 - (void)sourceThumbnailChangedAtIndex:(int)index {
327   const DesktopMediaList::Source& source = media_list_->GetSource(index);
328   NSImage* image = gfx::NSImageFromImageSkia(source.thumbnail);
330   DesktopMediaPickerItem* item = [items_ objectAtIndex:index];
331   [item setImageRepresentation:image];
332   [sourceBrowser_ reloadData];
335 @end  // @interface DesktopMediaPickerController