1 // Copyright (c) 2011 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. 4 5 #import <Cocoa/Cocoa.h> 6 7 #import "chrome/browser/ui/cocoa/theme_install_bubble_view.h" 8 9 #include "base/memory/scoped_nsobject.h" 10 #include "content/common/notification_service.h" 11 #include "grit/generated_resources.h" 12 #include "ui/base/l10n/l10n_util_mac.h" 13 14 namespace { 15 16 // The alpha of the bubble. 17 static const float kBubbleAlpha = 0.75; 18 19 // The roundedness of the edges of our bubble. 20 static const int kBubbleCornerRadius = 4; 21 22 // Padding around text in popup box. 23 static const int kTextHorizPadding = 90; 24 static const int kTextVertPadding = 45; 25 26 // Point size of the text in the box. 27 static const int kLoadingTextSize = 24; 28 29 } 30 31 // static 32 ThemeInstallBubbleView* ThemeInstallBubbleView::view_ = NULL; 33 34 // The Cocoa view to draw a gray rounded rect with "Loading..." in it. 35 @interface ThemeInstallBubbleViewCocoa : NSView { 36 @private 37 scoped_nsobject<NSAttributedString> message_; 38 39 NSRect grayRect_; 40 NSRect textRect_; 41 } 42 43 - (id)init; 44 45 // The size of the gray rect that will be drawn. 46 - (NSSize)preferredSize; 47 // Forces size calculations of where everything will be drawn. 48 - (void)layout; 49 50 @end 51 52 ThemeInstallBubbleView::ThemeInstallBubbleView(NSWindow* window) 53 : cocoa_view_([[ThemeInstallBubbleViewCocoa alloc] init]), 54 num_loads_extant_(1) { 55 DCHECK(window); 56 57 NSView* parent_view = [window contentView]; 58 NSRect parent_bounds = [parent_view bounds]; 59 if (parent_bounds.size.height < [cocoa_view_ preferredSize].height) 60 Close(); 61 62 // Close when theme has been installed. 63 registrar_.Add( 64 this, 65 NotificationType::BROWSER_THEME_CHANGED, 66 NotificationService::AllSources()); 67 68 // Close when we are installing an extension, not a theme. 69 registrar_.Add( 70 this, 71 NotificationType::NO_THEME_DETECTED, 72 NotificationService::AllSources()); 73 registrar_.Add( 74 this, 75 NotificationType::EXTENSION_INSTALLED, 76 NotificationService::AllSources()); 77 registrar_.Add( 78 this, 79 NotificationType::EXTENSION_INSTALL_ERROR, 80 NotificationService::AllSources()); 81 82 // Don't let the bubble overlap the confirm dialog. 83 registrar_.Add( 84 this, 85 NotificationType::EXTENSION_WILL_SHOW_CONFIRM_DIALOG, 86 NotificationService::AllSources()); 87 88 // Add the view. 89 [cocoa_view_ setFrame:parent_bounds]; 90 [cocoa_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 91 [parent_view addSubview:cocoa_view_ 92 positioned:NSWindowAbove 93 relativeTo:nil]; 94 [cocoa_view_ layout]; 95 } 96 97 ThemeInstallBubbleView::~ThemeInstallBubbleView() { 98 // Need to delete self; the real work happens in Close(). 99 } 100 101 void ThemeInstallBubbleView::Close() { 102 --num_loads_extant_; 103 if (num_loads_extant_ < 1) { 104 registrar_.RemoveAll(); 105 if (cocoa_view_ && [cocoa_view_ superview]) { 106 [cocoa_view_ removeFromSuperview]; 107 [cocoa_view_ release]; 108 } 109 view_ = NULL; 110 delete this; 111 // this is deleted; nothing more! 112 } 113 } 114 115 void ThemeInstallBubbleView::Observe(NotificationType type, 116 const NotificationSource& source, 117 const NotificationDetails& details) { 118 Close(); 119 } 120 121 // static 122 void ThemeInstallBubbleView::Show(NSWindow* window) { 123 if (view_) 124 ++view_->num_loads_extant_; 125 else 126 view_ = new ThemeInstallBubbleView(window); 127 } 128 129 @implementation ThemeInstallBubbleViewCocoa 130 131 - (id)init { 132 self = [super initWithFrame:NSZeroRect]; 133 if (self) { 134 NSString* loadingString = 135 l10n_util::GetNSStringWithFixup(IDS_THEME_LOADING_TITLE); 136 NSFont* loadingFont = [NSFont systemFontOfSize:kLoadingTextSize]; 137 NSColor* textColor = [NSColor whiteColor]; 138 NSDictionary* loadingAttrs = [NSDictionary dictionaryWithObjectsAndKeys: 139 loadingFont, NSFontAttributeName, 140 textColor, NSForegroundColorAttributeName, 141 nil]; 142 message_.reset([[NSAttributedString alloc] initWithString:loadingString 143 attributes:loadingAttrs]); 144 145 // TODO(avi): find a white-on-black spinner 146 } 147 return self; 148 } 149 150 - (NSSize)preferredSize { 151 NSSize size = [message_.get() size]; 152 size.width += kTextHorizPadding; 153 size.height += kTextVertPadding; 154 return size; 155 } 156 157 // Update the layout to keep the view centered when the window is resized. 158 - (void)resizeWithOldSuperviewSize:(NSSize)oldBoundsSize { 159 [super resizeWithOldSuperviewSize:oldBoundsSize]; 160 [self layout]; 161 } 162 163 - (void)layout { 164 NSRect bounds = [self bounds]; 165 166 grayRect_.size = [self preferredSize]; 167 grayRect_.origin.x = (bounds.size.width - grayRect_.size.width) / 2; 168 grayRect_.origin.y = bounds.size.height / 2; 169 170 textRect_.size = [message_.get() size]; 171 textRect_.origin.x = (bounds.size.width - [message_.get() size].width) / 2; 172 textRect_.origin.y = (bounds.size.height + kTextVertPadding) / 2; 173 } 174 175 - (void)drawRect:(NSRect)dirtyRect { 176 [[NSColor clearColor] set]; 177 NSRectFillUsingOperation([self bounds], NSCompositeSourceOver); 178 179 [[[NSColor blackColor] colorWithAlphaComponent:kBubbleAlpha] set]; 180 [[NSBezierPath bezierPathWithRoundedRect:grayRect_ 181 xRadius:kBubbleCornerRadius 182 yRadius:kBubbleCornerRadius] fill]; 183 184 [message_.get() drawInRect:textRect_]; 185 } 186 187 @end 188