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 #import "chrome/browser/ui/cocoa/download/download_item_controller.h"
7 #include "base/mac/bundle_locations.h"
8 #include "base/mac/mac_util.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/download/chrome_download_manager_delegate.h"
15 #include "chrome/browser/download/download_item_model.h"
16 #include "chrome/browser/download/download_shelf_context_menu.h"
17 #import "chrome/browser/themes/theme_properties.h"
18 #import "chrome/browser/themes/theme_service.h"
19 #import "chrome/browser/ui/cocoa/download/download_item_button.h"
20 #import "chrome/browser/ui/cocoa/download/download_item_cell.h"
21 #include "chrome/browser/ui/cocoa/download/download_item_mac.h"
22 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
23 #import "chrome/browser/ui/cocoa/themed_window.h"
24 #import "chrome/browser/ui/cocoa/ui_localizer.h"
25 #include "content/public/browser/download_item.h"
26 #include "content/public/browser/page_navigator.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
30 #include "ui/base/l10n/l10n_util_mac.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/base/text/text_elider.h"
33 #include "ui/gfx/font.h"
34 #include "ui/gfx/image/image.h"
36 using content::DownloadItem;
40 // NOTE: Mac currently doesn't use this like Windows does. Mac uses this to
41 // control the min size on the dangerous download text. TVL sent a query off to
42 // UX to fully spec all the the behaviors of download items and truncations
43 // rules so all platforms can get inline in the future.
44 const int kTextWidth = 140; // Pixels
46 // The maximum width in pixels for the file name tooltip.
47 const int kToolTipMaxWidth = 900;
50 // Helper to widen a view.
51 void WidenView(NSView* view, CGFloat widthChange) {
52 // If it is an NSBox, the autoresize of the contentView is the issue.
53 NSView* contentView = view;
54 if ([view isKindOfClass:[NSBox class]]) {
55 contentView = [(NSBox*)view contentView];
57 BOOL autoresizesSubviews = [contentView autoresizesSubviews];
58 if (autoresizesSubviews) {
59 [contentView setAutoresizesSubviews:NO];
62 NSRect frame = [view frame];
63 frame.size.width += widthChange;
64 [view setFrame:frame];
66 if (autoresizesSubviews) {
67 [contentView setAutoresizesSubviews:YES];
73 class DownloadShelfContextMenuMac : public DownloadShelfContextMenu {
75 DownloadShelfContextMenuMac(DownloadItem* downloadItem,
76 content::PageNavigator* navigator)
77 : DownloadShelfContextMenu(downloadItem, navigator) { }
79 // DownloadShelfContextMenu::GetMenuModel is protected.
80 using DownloadShelfContextMenu::GetMenuModel;
83 @interface DownloadItemController (Private)
84 - (void)themeDidChangeNotification:(NSNotification*)aNotification;
85 - (void)updateTheme:(ui::ThemeProvider*)themeProvider;
86 - (void)setState:(DownoadItemState)state;
89 // Implementation of DownloadItemController
91 @implementation DownloadItemController
93 - (id)initWithDownload:(DownloadItem*)downloadItem
94 shelf:(DownloadShelfController*)shelf
95 navigator:(content::PageNavigator*)navigator {
96 if ((self = [super initWithNibName:@"DownloadItem"
97 bundle:base::mac::FrameworkBundle()])) {
98 // Must be called before [self view], so that bridge_ is set in awakeFromNib
99 bridge_.reset(new DownloadItemMac(downloadItem, self));
100 menuBridge_.reset(new DownloadShelfContextMenuMac(downloadItem, navigator));
102 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
103 [defaultCenter addObserver:self
104 selector:@selector(themeDidChangeNotification:)
105 name:kBrowserThemeDidChangeNotification
110 creationTime_ = base::Time::Now();
111 font_list_.reset(new gfx::FontList(
112 ui::ResourceBundle::GetSharedInstance().GetFontList(
113 ui::ResourceBundle::BaseFont)));
119 [[NSNotificationCenter defaultCenter] removeObserver:self];
120 [progressView_ setController:nil];
121 [[self view] removeFromSuperview];
125 - (void)awakeFromNib {
126 [progressView_ setController:self];
128 [self setStateFromDownload:bridge_->download_model()];
130 GTMUILocalizerAndLayoutTweaker* localizerAndLayoutTweaker =
131 [[[GTMUILocalizerAndLayoutTweaker alloc] init] autorelease];
132 [localizerAndLayoutTweaker applyLocalizer:localizer_ tweakingUI:[self view]];
134 // The strings are based on the download item's name, sizing tweaks have to be
136 DCHECK(buttonTweaker_ != nil);
137 CGFloat widthChange = [buttonTweaker_ changedWidth];
138 // If it's a dangerous download, size the two lines so the text/filename
139 // is always visible.
140 if ([self isDangerousMode]) {
142 [GTMUILocalizerAndLayoutTweaker
143 sizeToFitFixedHeightTextField:dangerousDownloadLabel_
144 minWidth:kTextWidth];
146 // Grow the parent views
147 WidenView([self view], widthChange);
148 WidenView(dangerousDownloadView_, widthChange);
149 // Slide the two buttons over.
150 NSPoint frameOrigin = [buttonTweaker_ frame].origin;
151 frameOrigin.x += widthChange;
152 [buttonTweaker_ setFrameOrigin:frameOrigin];
155 [self updateToolTip];
158 - (void)setStateFromDownload:(DownloadItemModel*)downloadModel {
159 DCHECK_EQ([self download], downloadModel->download());
161 // Handle dangerous downloads.
162 if (downloadModel->IsDangerous()) {
163 [self setState:kDangerous];
165 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
166 NSString* dangerousWarning;
167 NSString* confirmButtonTitle;
171 base::SysUTF16ToNSString(downloadModel->GetWarningText(
172 *font_list_, kTextWidth));
174 base::SysUTF16ToNSString(downloadModel->GetWarningConfirmButtonText());
175 if (downloadModel->IsMalicious())
176 alertIcon = rb.GetNativeImageNamed(IDR_SAFEBROWSING_WARNING).ToNSImage();
178 alertIcon = rb.GetNativeImageNamed(IDR_WARNING).ToNSImage();
180 [image_ setImage:alertIcon];
181 DCHECK(dangerousWarning);
182 [dangerousDownloadLabel_ setStringValue:dangerousWarning];
183 DCHECK(confirmButtonTitle);
184 [dangerousDownloadConfirmButton_ setTitle:confirmButtonTitle];
188 // Set path to draggable download on completion.
189 if (downloadModel->download()->GetState() == DownloadItem::COMPLETE)
190 [progressView_ setDownload:downloadModel->download()->GetTargetFilePath()];
192 [cell_ setStateFromDownload:downloadModel];
195 - (void)setIcon:(NSImage*)icon {
196 [cell_ setImage:icon];
200 // We are deleted after this!
201 [shelf_ remove:self];
204 - (void)updateVisibility:(id)sender {
205 if ([[self view] window])
206 [self updateTheme:[[[self view] window] themeProvider]];
208 NSView* view = [self view];
209 NSRect containerFrame = [[view superview] frame];
210 [view setHidden:(NSMaxX([view frame]) > NSWidth(containerFrame))];
213 - (void)downloadWasOpened {
214 [shelf_ downloadWasOpened:self];
217 - (IBAction)handleButtonClick:(id)sender {
218 NSEvent* event = [NSApp currentEvent];
219 DownloadItem* download = [self download];
220 if ([event modifierFlags] & NSCommandKeyMask) {
221 // Let cmd-click show the file in Finder, like e.g. in Safari and Spotlight.
222 download->ShowDownloadInShell();
224 download->OpenDownload();
228 - (NSSize)preferredSize {
229 if (state_ == kNormal)
230 return [progressView_ frame].size;
231 DCHECK_EQ(kDangerous, state_);
232 return [dangerousDownloadView_ frame].size;
235 - (DownloadItem*)download {
236 return bridge_->download_model()->download();
239 - (ui::MenuModel*)contextMenuModel {
240 return menuBridge_->GetMenuModel();
243 - (void)updateToolTip {
244 string16 tooltip_text =
245 bridge_->download_model()->GetTooltipText(*font_list_, kToolTipMaxWidth);
246 [progressView_ setToolTip:base::SysUTF16ToNSString(tooltip_text)];
249 - (void)clearDangerousMode {
250 [self setState:kNormal];
251 // The state change hide the dangerouse download view and is now showing the
252 // download progress view. This means the view is likely to be a different
253 // size, so trigger a shelf layout to fix up spacing.
254 [shelf_ layoutItems];
257 - (BOOL)isDangerousMode {
258 return state_ == kDangerous;
261 - (void)setState:(DownoadItemState)state {
265 if (state_ == kNormal) {
266 [progressView_ setHidden:NO];
267 [dangerousDownloadView_ setHidden:YES];
269 DCHECK_EQ(kDangerous, state_);
270 [progressView_ setHidden:YES];
271 [dangerousDownloadView_ setHidden:NO];
273 // NOTE: Do not relayout the shelf, as this could get called during initial
274 // setup of the the item, so the localized text and sizing might not have
278 // Called after a theme change took place, possibly for a different profile.
279 - (void)themeDidChangeNotification:(NSNotification*)notification {
280 [self updateTheme:[[[self view] window] themeProvider]];
283 // Adapt appearance to the current theme. Called after theme changes and before
284 // this is shown for the first time.
285 - (void)updateTheme:(ui::ThemeProvider*)themeProvider {
289 NSColor* color = themeProvider->GetNSColor(ThemeProperties::COLOR_TAB_TEXT);
290 [dangerousDownloadLabel_ setTextColor:color];
293 - (IBAction)saveDownload:(id)sender {
294 // The user has confirmed a dangerous download. We record how quickly the
295 // user did this to detect whether we're being clickjacked.
296 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download",
297 base::Time::Now() - creationTime_);
298 // This will change the state and notify us.
299 bridge_->download_model()->download()->ValidateDangerousDownload();
302 - (IBAction)discardDownload:(id)sender {
303 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download",
304 base::Time::Now() - creationTime_);
305 DownloadItem* download = bridge_->download_model()->download();
307 // WARNING: we are deleted at this point. Don't access 'this'.