Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(90)

Side by Side Diff: chrome/browser/cocoa/extensions/browser_actions_controller.mm

Issue 366029: Initial change for the implementation of browser actions on the mac.... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: '' Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2009 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 "browser_actions_controller.h"
6
7 #include <string>
8
9 #include "app/gfx/canvas_paint.h"
10 #include "base/sys_string_conversions.h"
11 #include "chrome/browser/browser.h"
12 #include "chrome/browser/cocoa/toolbar_button_cell.h"
13 #include "chrome/browser/extensions/extension_browser_event_router.h"
14 #include "chrome/browser/extensions/extensions_service.h"
15 #include "chrome/browser/extensions/extension_tabs_module.h"
16 #include "chrome/browser/extensions/image_loading_tracker.h"
17 #include "chrome/browser/profile.h"
18 #include "chrome/browser/tab_contents/tab_contents.h"
19 #include "chrome/common/notification_observer.h"
20 #include "chrome/common/notification_registrar.h"
21 #include "skia/ext/skia_utils_mac.h"
22
23 static const CGFloat kBrowserActionBadgeOriginYOffset = -4;
24
25 // Since the container is the maximum height of the toolbar, we have to move the
26 // buttons up by this amount in order to have them look vertically centered
27 // within the toolbar.
28 static const CGFloat kBrowserActionOriginYOffset = 5;
29
30 // The size of each button on the toolbar.
31 static const CGFloat kBrowserActionHeight = 27;
32 extern const CGFloat kBrowserActionWidth = 29;
33
34 // The padding between browser action buttons.
35 extern const CGFloat kBrowserActionButtonPadding = 3;
36
37 NSString* const kBrowserActionsChangedNotification = @"BrowserActionsChanged";
38
39 @interface BrowserActionBadgeView : NSView {
40 @private
41 // The current tab ID used when drawing the badge.
42 int tabId_;
43
44 // The action we're drawing the badge for. Weak.
45 ExtensionAction* extensionAction_;
46 }
47
48 @property(readwrite, nonatomic) int tabId;
49 @property(readwrite, nonatomic) ExtensionAction* extensionAction;
50
51 @end
52
53 @implementation BrowserActionBadgeView
54
55 - (void)drawRect:(NSRect)dirtyRect {
56 // CanvasPaint draws its content to the current NSGraphicsContext in its
57 // destructor. If anything needs to be drawn afterwards, then enclose this
58 // in a nested block.
59 NSRect badgeBounds = [self bounds];
60 badgeBounds.origin.y += kBrowserActionBadgeOriginYOffset;
61 gfx::CanvasPaint canvas(badgeBounds, false);
62 canvas.set_composite_alpha(true);
63 gfx::Rect boundingRect(NSRectToCGRect(badgeBounds));
64 extensionAction_->PaintBadge(&canvas, boundingRect, tabId_);
65 }
66
67 @synthesize tabId = tabId_;
68 @synthesize extensionAction = extensionAction_;
69
70 @end
71
72 class ExtensionImageTrackerBridge;
73
74 @interface BrowserActionButton : NSButton {
75 @private
76 scoped_ptr<ExtensionImageTrackerBridge> imageLoadingBridge_;
77
78 scoped_nsobject<NSImage> defaultIcon_;
79
80 scoped_nsobject<NSImage> tabSpecificIcon_;
81
82 scoped_nsobject<NSView> badgeView_;
83
84 // The extension for this button. Weak.
85 Extension* extension_;
86
87 // Weak. Owns us.
88 BrowserActionsController* controller_;
89 }
90
91 - (id)initWithExtension:(Extension*)extension
92 controller:(BrowserActionsController*)controller
93 xOffset:(int)xOffset;
94
95 - (void)setDefaultIcon:(NSImage*)image;
96
97 - (void)setTabSpecificIcon:(NSImage*)image;
98
99 - (void)updateState;
100
101 @property(readonly, nonatomic) Extension* extension;
102
103 @end
104
105 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to
106 // the extension's button.
107 class ExtensionImageTrackerBridge : public NotificationObserver,
108 public ImageLoadingTracker::Observer {
109 public:
110 ExtensionImageTrackerBridge(BrowserActionButton* owner, Extension* extension)
111 : owner_(owner),
112 tracker_(NULL) {
113 // The Browser Action API does not allow the default icon path to be
114 // changed at runtime, so we can load this now and cache it.
115 std::string path = extension->browser_action()->default_icon_path();
116 if (!path.empty()) {
117 tracker_ = new ImageLoadingTracker(this, 1);
118 tracker_->PostLoadImageTask(extension->GetResource(path),
119 gfx::Size(Extension::kBrowserActionIconMaxSize,
120 Extension::kBrowserActionIconMaxSize));
121 }
122 registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED,
123 Source<ExtensionAction>(extension->browser_action()));
124 }
125
126 ~ExtensionImageTrackerBridge() {
127 if (tracker_)
128 tracker_->StopTrackingImageLoad();
129 }
130
131 // ImageLoadingTracker::Observer implementation.
132 void OnImageLoaded(SkBitmap* image, size_t index) {
133 if (image)
134 [owner_ setDefaultIcon:gfx::SkBitmapToNSImage(*image)];
135 tracker_ = NULL;
136 [owner_ updateState];
137 }
138
139 // Overridden from NotificationObserver.
140 void Observe(NotificationType type,
141 const NotificationSource& source,
142 const NotificationDetails& details) {
143 if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED)
144 [owner_ updateState];
145 else
146 NOTREACHED();
147 }
148
149 private:
150 // Weak. Owns us.
151 BrowserActionButton* owner_;
152
153 // Loads the button's icons for us on the file thread. Weak.
154 ImageLoadingTracker* tracker_;
155
156 // Used for registering to receive notifications and automatic clean up.
157 NotificationRegistrar registrar_;
158
159 DISALLOW_COPY_AND_ASSIGN(ExtensionImageTrackerBridge);
160 };
161
162 @implementation BrowserActionButton
163
164 - (id)initWithExtension:(Extension*)extension
165 controller:(BrowserActionsController*)controller
166 xOffset:(int)xOffset {
167 NSRect frame = NSMakeRect(xOffset,
168 kBrowserActionOriginYOffset,
169 kBrowserActionWidth,
170 kBrowserActionHeight);
171 if ((self = [super initWithFrame:frame])) {
172 ToolbarButtonCell* cell = [[[ToolbarButtonCell alloc] init] autorelease];
173 // [NSButton setCell:] warns to NOT use setCell: other than in the
174 // initializer of a control. However, we are using a basic
175 // NSButton whose initializer does not take an NSCell as an
176 // object. To honor the assumed semantics, we do nothing with
177 // NSButton between alloc/init and setCell:.
178 [self setCell:cell];
179 [self setTitle:@""];
180 [self setButtonType:NSMomentaryChangeButton];
181 [self setShowsBorderOnlyWhileMouseInside:YES];
182
183 [self setTarget:controller];
184 [self setAction:@selector(browserActionClicked:)];
185
186 extension_ = extension;
187 controller_ = controller;
188 imageLoadingBridge_.reset(new ExtensionImageTrackerBridge(self, extension));
189
190 NSRect badgeFrame = [self bounds];
191 badgeView_.reset([[BrowserActionBadgeView alloc] initWithFrame:badgeFrame]);
192 [badgeView_ setTabId:[controller currentTabId]];
193 [badgeView_ setExtensionAction:extension->browser_action()];
194 [self addSubview:badgeView_];
195
196 [self updateState];
197 }
198
199 return self;
200 }
201
202 - (void)setDefaultIcon:(NSImage*)image {
203 defaultIcon_.reset([image retain]);
204 }
205
206 - (void)setTabSpecificIcon:(NSImage*)image {
207 tabSpecificIcon_.reset([image retain]);
208 }
209
210 - (void)updateState {
211 int tabId = [controller_ currentTabId];
212 if (tabId < 0)
213 return;
214
215 std::string tooltip = extension_->browser_action()->GetTitle(tabId);
216 if (!tooltip.empty())
217 [self setToolTip:base::SysUTF8ToNSString(tooltip)];
218
219 SkBitmap image = extension_->browser_action()->GetIcon(tabId);
220 if (!image.isNull()) {
221 [self setTabSpecificIcon:gfx::SkBitmapToNSImage(image)];
222 [self setImage:tabSpecificIcon_];
223 } else if (defaultIcon_) {
224 [self setImage:defaultIcon_];
225 }
226
227 [badgeView_ setTabId:tabId];
228
229 [self setNeedsDisplay:YES];
230 }
231
232 @synthesize extension = extension_;
233
234 @end
235
236 @interface BrowserActionsController(Private)
237
238 - (void)createActionButtonForExtension:(Extension*)extension;
239 - (void)removeActionButtonForExtension:(Extension*)extension;
240 - (void)repositionActionButtons;
241
242 @end
243
244 // A helper class to proxy extension notifications to the view controller's
245 // appropriate methods.
246 class ExtensionsServiceObserverBridge : public NotificationObserver {
247 public:
248 ExtensionsServiceObserverBridge(BrowserActionsController* owner,
249 Profile* profile) : owner_(owner) {
250 registrar_.Add(this, NotificationType::EXTENSION_LOADED,
251 Source<Profile>(profile));
252 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
253 Source<Profile>(profile));
254 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED,
255 Source<Profile>(profile));
256 registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
257 Source<Profile>(profile));
258 }
259
260 // Runs |owner_|'s method corresponding to the event type received from the
261 // notification system.
262 // Overridden from NotificationObserver.
263 void Observe(NotificationType type,
264 const NotificationSource& source,
265 const NotificationDetails& details) {
266 switch (type.value) {
267 case NotificationType::EXTENSION_LOADED: {
268 Extension* extension = Details<Extension>(details).ptr();
269 [owner_ createActionButtonForExtension:extension];
270 [owner_ browserActionVisibilityHasChanged];
271 break;
272 }
273 case NotificationType::EXTENSION_UNLOADED:
274 case NotificationType::EXTENSION_UNLOADED_DISABLED: {
275 Extension* extension = Details<Extension>(details).ptr();
276 [owner_ removeActionButtonForExtension:extension];
277 [owner_ browserActionVisibilityHasChanged];
278 break;
279 }
280 case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE:
281 //if (Details<ExtensionHost>(popup_->host()) != details)
282 // return;
283 [owner_ hidePopup];
284 break;
285 default:
286 NOTREACHED() << L"Unexpected notification";
287 }
288 }
289
290 private:
291 // The object we need to inform when we get a notification. Weak. Owns us.
292 BrowserActionsController* owner_;
293
294 // Used for registering to receive notifications and automatic clean up.
295 NotificationRegistrar registrar_;
296
297 DISALLOW_COPY_AND_ASSIGN(ExtensionsServiceObserverBridge);
298 };
299
300 @implementation BrowserActionsController
301
302 - (id)initWithBrowser:(Browser*)browser
303 containerView:(NSView*)container {
304 DCHECK(browser && container);
305
306 if ((self = [super init])) {
307 browser_ = browser;
308 profile_ = browser->profile();
309
310 containerView_ = container;
311 [containerView_ setHidden:YES];
312 observer_.reset(new ExtensionsServiceObserverBridge(self, profile_));
313 buttons_.reset([[NSMutableDictionary alloc] init]);
314 buttonOrder_.reset([[NSMutableArray alloc] init]);
315 }
316
317 return self;
318 }
319
320 - (void)hidePopup {
321 NOTIMPLEMENTED();
322 }
323
324 - (void)browserActionVisibilityHasChanged {
325 [containerView_ setNeedsDisplay:YES];
326 }
327
328 - (void)createButtons {
329 ExtensionsService* extensionsService = profile_->GetExtensionsService();
330 if (!extensionsService) // |extensionsService| can be NULL in Incognito.
331 return;
332
333 for (size_t i = 0; i < extensionsService->extensions()->size(); ++i) {
334 Extension* extension = extensionsService->GetExtensionById(
335 extensionsService->extensions()->at(i)->id(), false);
336 if (extension->browser_action()) {
337 [self createActionButtonForExtension:extension];
338 }
339 }
340 }
341
342 - (void)createActionButtonForExtension:(Extension*)extension {
343 if (!extension->browser_action())
344 return;
345
346 if ([buttons_ count] == 0) {
347 // Only call if we're adding our first button, otherwise it will be shown
348 // already.
349 [containerView_ setHidden:NO];
350 }
351
352 int xOffset =
353 [buttons_ count] * (kBrowserActionWidth + kBrowserActionButtonPadding);
354 BrowserActionButton* newButton =
355 [[[BrowserActionButton alloc] initWithExtension:extension
356 controller:self
357 xOffset:xOffset] autorelease];
358 NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
359 [buttons_ setObject:newButton forKey:buttonKey];
360 [buttonOrder_ addObject:newButton];
361 [containerView_ addSubview:newButton];
362
363 [[NSNotificationCenter defaultCenter]
364 postNotificationName:kBrowserActionsChangedNotification object:self];
365 }
366
367 - (void)removeActionButtonForExtension:(Extension*)extension {
368 NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
369
370 BrowserActionButton* button = [buttons_ objectForKey:buttonKey];
371 [button removeFromSuperview];
372 [buttons_ removeObjectForKey:buttonKey];
373 [buttonOrder_ removeObject:button];
374 if ([buttons_ count] == 0) {
375 // No more buttons? Hide the container.
376 [containerView_ setHidden:YES];
377 } else {
378 // repositionActionButtons only needs to be called if removing a browser
379 // action button because adding one will always append to the end of the
380 // container, while removing one may require that those to the right of it
381 // be shifted to the left.
382 [self repositionActionButtons];
383 }
384 [[NSNotificationCenter defaultCenter]
385 postNotificationName:kBrowserActionsChangedNotification object:self];
386 }
387
388 - (void)repositionActionButtons {
389 for (NSUInteger i = 0; i < [buttonOrder_ count]; ++i) {
390 CGFloat xOffset = i * (kBrowserActionWidth + kBrowserActionButtonPadding);
391 BrowserActionButton* button = [buttonOrder_ objectAtIndex:i];
392 NSRect buttonFrame = [button frame];
393 buttonFrame.origin.x = xOffset;
394 [button setFrame:buttonFrame];
395 }
396 }
397
398 - (int)buttonCount {
399 return [buttons_ count];
400 }
401
402 - (void)browserActionClicked:(BrowserActionButton*)sender {
403 ExtensionAction* action = [sender extension]->browser_action();
404 if (action->has_popup()) {
405 // Popups are not implemented for Mac yet.
406 NOTIMPLEMENTED();
407 } else {
408 ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted(
409 profile_, action->extension_id(), browser_);
410 }
411 }
412
413 - (int)currentTabId {
414 TabContents* selected_tab = browser_->GetSelectedTabContents();
415 if (!selected_tab)
416 return -1;
417
418 return selected_tab->controller().session_id().id();
419 }
420
421 @end
OLDNEW
« no previous file with comments | « chrome/browser/cocoa/extensions/browser_actions_controller.h ('k') | chrome/browser/cocoa/toolbar_controller.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698