1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
|
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/cocoa/toolbar_controller.h"
#include "base/mac_util.h"
#include "base/sys_string_conversions.h"
#include "base/gfx/rect.h"
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/autocomplete/autocomplete_popup_view.h"
#import "chrome/browser/cocoa/autocomplete_text_field.h"
#import "chrome/browser/cocoa/autocomplete_text_field_editor.h"
#import "chrome/browser/cocoa/back_forward_menu_controller.h"
#import "chrome/browser/cocoa/gradient_button_cell.h"
#import "chrome/browser/cocoa/location_bar_view_mac.h"
#include "chrome/browser/cocoa/nsimage_cache.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/toolbar_model.h"
#include "chrome/common/notification_details.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
// Name of image in the bundle for the yellow of the star icon.
static NSString* const kStarredImageName = @"starred.pdf";
// Height of the toolbar in pixels when the bookmark bar is closed.
static const float kBaseToolbarHeight = 36.0;
// Overlap (in pixels) between the toolbar and the bookmark bar.
static const float kBookmarkBarOverlap = 7.0;
@interface ToolbarController(Private)
- (void)initCommandStatus:(CommandUpdater*)commands;
- (void)prefChanged:(std::wstring*)prefName;
@end
namespace {
// A C++ class used to correctly position the autocomplete popup.
class AutocompletePopupPositionerMac : public AutocompletePopupPositioner {
public:
AutocompletePopupPositionerMac(ToolbarController* controller)
: controller_(controller) { }
virtual ~AutocompletePopupPositionerMac() { }
// Overridden from AutocompletePopupPositioner.
virtual gfx::Rect GetPopupBounds() const {
return [controller_ autocompletePopupPosition];
}
private:
ToolbarController* controller_; // weak, owns us
};
} // namespace
namespace ToolbarControllerInternal {
// A C++ class registered for changes in preferences. Bridges the
// notification back to the ToolbarController.
class PrefObserverBridge : public NotificationObserver {
public:
PrefObserverBridge(ToolbarController* controller)
: controller_(controller) { }
// Overridden from NotificationObserver:
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type == NotificationType::PREF_CHANGED)
[controller_ prefChanged:Details<std::wstring>(details).ptr()];
}
private:
ToolbarController* controller_; // weak, owns us
};
} // namespace ToolbarControllerInternal
@implementation ToolbarController
- (id)initWithModel:(ToolbarModel*)model
commands:(CommandUpdater*)commands
profile:(Profile*)profile
browser:(Browser*)browser
resizeDelegate:(id<ViewResizer>)resizeDelegate
bookmarkDelegate:(id<BookmarkURLOpener>)delegate {
DCHECK(model && commands && profile);
if ((self = [super initWithNibName:@"Toolbar"
bundle:mac_util::MainAppBundle()])) {
toolbarModel_ = model;
commands_ = commands;
profile_ = profile;
browser_ = browser;
resizeDelegate_ = resizeDelegate;
bookmarkBarDelegate_ = delegate;
hasToolbar_ = YES;
// Register for notificaotions about state changes for the toolbar buttons
commandObserver_.reset(new CommandObserverBridge(self, commands));
commandObserver_->ObserveCommand(IDC_BACK);
commandObserver_->ObserveCommand(IDC_FORWARD);
commandObserver_->ObserveCommand(IDC_RELOAD);
commandObserver_->ObserveCommand(IDC_HOME);
commandObserver_->ObserveCommand(IDC_STAR);
}
return self;
}
- (void)dealloc {
// Make sure any code in the base class which assumes [self view] is
// the "parent" view continues to work.
hasToolbar_ = YES;
if (trackingArea_.get())
[[self view] removeTrackingArea:trackingArea_.get()];
[super dealloc];
}
// Called after the view is done loading and the outlets have been hooked up.
// Now we can hook up bridges that rely on UI objects such as the location
// bar and button state.
- (void)awakeFromNib {
[self initCommandStatus:commands_];
popupPositioner_.reset(new AutocompletePopupPositionerMac(self));
locationBarView_.reset(new LocationBarViewMac(locationBar_,
popupPositioner_.get(),
commands_, toolbarModel_,
profile_));
[locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
// Register pref observers for the optional home and page/options buttons
// and then add them to the toolbar them based on those prefs.
prefObserver_.reset(new ToolbarControllerInternal::PrefObserverBridge(self));
PrefService* prefs = profile_->GetPrefs();
showHomeButton_.Init(prefs::kShowHomeButton, prefs, prefObserver_.get());
showPageOptionButtons_.Init(prefs::kShowPageOptionsButtons, prefs,
prefObserver_.get());
[self showOptionalHomeButton];
[self showOptionalPageWrenchButtons];
// Create a sub-controller for the bookmark bar.
bookmarkBarController_.reset([[BookmarkBarController alloc]
initWithProfile:profile_
initialWidth:NSWidth([[self view] frame])
resizeDelegate:self
urlDelegate:bookmarkBarDelegate_]);
// Add bookmark bar to the view hierarchy. This also triggers the
// nib load. The bookmark bar is defined (in the nib) to be
// bottom-aligned to it's parent view (among other things), so
// position and resize properties don't need to be set.
[[self view] addSubview:[bookmarkBarController_ view]];
// We don't want to try and show the bar before it gets placed in
// it's parent view, so this step shoudn't be inside the bookmark
// bar controller's awakeFromNib.
[bookmarkBarController_ showIfNeeded];
// Create the controllers for the back/forward menus.
backMenuController_.reset([[BackForwardMenuController alloc]
initWithBrowser:browser_
modelType:BACK_FORWARD_MENU_TYPE_BACK
button:backButton_]);
forwardMenuController_.reset([[BackForwardMenuController alloc]
initWithBrowser:browser_
modelType:BACK_FORWARD_MENU_TYPE_FORWARD
button:forwardButton_]);
// For a popup window, the toolbar is really just a location bar
// (see override for [ToolbarController view], below). When going
// fullscreen, we remove the toolbar controller's view from the view
// hierarchy. Calling [locationBar_ removeFromSuperview] when going
// fullscreen causes it to get released, making us unhappy
// (http://crbug.com/18551). We avoid the problem by incrementing
// the retain count of the location bar; use of the scoped object
// helps us remember to release it.
locationBarRetainer_.reset([locationBar_ retain]);
trackingArea_.reset(
[[NSTrackingArea alloc] initWithRect:NSZeroRect // Ignored
options:NSTrackingMouseMoved |
NSTrackingInVisibleRect |
NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways
owner:self
userInfo:nil]);
[[self view] addTrackingArea:trackingArea_.get()];
}
- (void)removeFromSuperview {
NSLog(@"remove");
}
- (void)mouseExited:(NSEvent*)theEvent {
[[hoveredButton_ cell] setMouseInside:NO animate:YES];
hoveredButton_ = nil;
}
- (void)mouseMoved:(NSEvent *)theEvent {
NSButton *targetView = (NSButton *)[[self view]
hitTest:[theEvent locationInWindow]];
if (![targetView isKindOfClass:[NSButton class]]) targetView = nil;
if (hoveredButton_ != targetView) {
[[hoveredButton_ cell] setMouseInside:NO animate:YES];
[[targetView cell] setMouseInside:YES animate:YES];
hoveredButton_ = targetView;
}
}
- (void)mouseEntered:(NSEvent*)event {
[self mouseMoved:event];
}
- (void)resizeView:(NSView*)view newHeight:(float)height {
DCHECK(view == [bookmarkBarController_ view]);
// The bookmark bar is always rooted at the bottom of the toolbar view, with
// width equal to the toolbar's width. The toolbar view is resized to
// accomodate the new bookmark bar height.
NSRect frame = NSMakeRect(0, 0, [[self view] bounds].size.width, height);
[view setFrame:frame];
float newToolbarHeight = kBaseToolbarHeight + height - kBookmarkBarOverlap;
if (newToolbarHeight < kBaseToolbarHeight)
newToolbarHeight = kBaseToolbarHeight;
[resizeDelegate_ resizeView:[self view] newHeight:newToolbarHeight];
}
- (LocationBar*)locationBar {
return locationBarView_.get();
}
- (void)focusLocationBar {
if (locationBarView_.get()) {
locationBarView_->FocusLocation();
}
}
// Called when the state for a command changes to |enabled|. Update the
// corresponding UI element.
- (void)enabledStateChangedForCommand:(NSInteger)command enabled:(BOOL)enabled {
NSButton* button = nil;
switch (command) {
case IDC_BACK:
button = backButton_;
break;
case IDC_FORWARD:
button = forwardButton_;
break;
case IDC_HOME:
button = homeButton_;
break;
case IDC_STAR:
button = starButton_;
break;
}
[button setEnabled:enabled];
}
// Init the enabled state of the buttons on the toolbar to match the state in
// the controller.
- (void)initCommandStatus:(CommandUpdater*)commands {
[backButton_ setEnabled:commands->IsCommandEnabled(IDC_BACK) ? YES : NO];
[forwardButton_
setEnabled:commands->IsCommandEnabled(IDC_FORWARD) ? YES : NO];
[reloadButton_ setEnabled:commands->IsCommandEnabled(IDC_RELOAD) ? YES : NO];
[homeButton_ setEnabled:commands->IsCommandEnabled(IDC_HOME) ? YES : NO];
[starButton_ setEnabled:commands->IsCommandEnabled(IDC_STAR) ? YES : NO];
}
- (void)updateToolbarWithContents:(TabContents*)tab
shouldRestoreState:(BOOL)shouldRestore {
locationBarView_->Update(tab, shouldRestore ? true : false);
}
- (void)setStarredState:(BOOL)isStarred {
NSImage* starImage = nil;
if (isStarred)
starImage = nsimage_cache::ImageNamed(kStarredImageName);
[(GradientButtonCell*)[starButton_ cell] setUnderlayImage:starImage];
}
- (void)setIsLoading:(BOOL)isLoading {
NSString* imageName = @"go_Template.pdf";
NSInteger tag = IDC_GO;
if (isLoading) {
imageName = @"stop_Template.pdf";
tag = IDC_STOP;
}
NSImage* stopStartImage = nsimage_cache::ImageNamed(imageName);
[stopStartImage setTemplate:YES];
[goButton_ setImage:stopStartImage];
[goButton_ setTag:tag];
}
- (void)setHasToolbar:(BOOL)toolbar {
[self view]; // force nib loading
hasToolbar_ = toolbar;
// App mode allows turning off the location bar as well.
// TODO(???): add more code here when implementing app mode to allow
// turning off both toolbar AND location bar.
// TODO(jrg): add mode code to make the location bar NOT editable
// when in a pop-up.
}
- (NSView*)view {
if (hasToolbar_)
return [super view];
return locationBar_;
}
- (BookmarkBarController*)bookmarkBarController {
// Browser has a FEATURE_BOOKMARKBAR but it is ignored by Safari
// when using window.open(); the logic seems to be "if no toolbar,
// no bookmark bar".
// TODO(jrg): investigate non-Mac Chrome behavior and possibly expand this.
if (hasToolbar_ == NO)
return nil;
return bookmarkBarController_.get();
}
- (id)customFieldEditorForObject:(id)obj {
if (obj == locationBar_) {
// Lazilly construct Field editor, Cocoa UI code always runs on the
// same thread, so there shoudn't be a race condition here.
if (autocompleteTextFieldEditor_.get() == nil) {
autocompleteTextFieldEditor_.reset(
[[AutocompleteTextFieldEditor alloc] init]);
}
// This needs to be called every time, otherwise notifications
// aren't sent correctly.
DCHECK(autocompleteTextFieldEditor_.get());
[autocompleteTextFieldEditor_.get() setFieldEditor:YES];
return autocompleteTextFieldEditor_.get();
}
return nil;
}
// Returns an array of views in the order of the outlets above.
- (NSArray*)toolbarViews {
return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_,
homeButton_, starButton_, goButton_, pageButton_, wrenchButton_,
locationBar_, nil];
}
// Moves |rect| to the right by |delta|, keeping the right side fixed by
// shrinking the width to compensate. Passing a negative value for |deltaX|
// moves to the left and increases the width.
- (NSRect)adjustRect:(NSRect)rect byAmount:(float)deltaX {
NSRect frame = NSOffsetRect(rect, deltaX, 0);
frame.size.width -= deltaX;
return frame;
}
// Computes the padding between the buttons that should have a separation from
// the positions in the nib. Since the forward and reload buttons are always
// visible, we use those buttons as the canonical spacing.
- (float)interButtonSpacing {
NSRect forwardFrame = [forwardButton_ frame];
NSRect reloadFrame = [reloadButton_ frame];
DCHECK(NSMinX(reloadFrame) > NSMaxX(forwardFrame));
return NSMinX(reloadFrame) - NSMaxX(forwardFrame);
}
// Show or hide the home button based on the pref.
- (void)showOptionalHomeButton {
BOOL hide = showHomeButton_.GetValue() ? NO : YES;
if (hide == [homeButton_ isHidden])
return; // Nothing to do, view state matches pref state.
// Always shift the star and text field by the width of the home button plus
// the appropriate gap width. If we're hiding the button, we have to
// reverse the direction of the movement (to the left).
float moveX = [self interButtonSpacing] + [homeButton_ frame].size.width;
if (hide)
moveX *= -1; // Reverse the direction of the move.
[starButton_ setFrame:NSOffsetRect([starButton_ frame], moveX, 0)];
[locationBar_ setFrame:[self adjustRect:[locationBar_ frame]
byAmount:moveX]];
[homeButton_ setHidden:hide];
}
// Show or hide the page and wrench buttons based on the pref.
- (void)showOptionalPageWrenchButtons {
DCHECK([pageButton_ isHidden] == [wrenchButton_ isHidden]);
BOOL hide = showPageOptionButtons_.GetValue() ? NO : YES;
if (hide == [pageButton_ isHidden])
return; // Nothing to do, view state matches pref state.
// Shift the go button and resize the text field by the width of the
// page/wrench buttons plus two times the gap width. If we're showing the
// buttons, we have to reverse the direction of movement (to the left). Unlike
// the home button above, we only ever have to resize the text field, we don't
// have to move it.
float moveX = 2 * [self interButtonSpacing] + NSWidth([pageButton_ frame]) +
NSWidth([wrenchButton_ frame]);
if (!hide)
moveX *= -1; // Reverse the direction of the move.
[goButton_ setFrame:NSOffsetRect([goButton_ frame], moveX, 0)];
NSRect locationFrame = [locationBar_ frame];
locationFrame.size.width += moveX;
[locationBar_ setFrame:locationFrame];
[pageButton_ setHidden:hide];
[wrenchButton_ setHidden:hide];
}
- (void)prefChanged:(std::wstring*)prefName {
if (!prefName) return;
if (*prefName == prefs::kShowHomeButton) {
[self showOptionalHomeButton];
} else if (*prefName == prefs::kShowPageOptionsButtons) {
[self showOptionalPageWrenchButtons];
}
}
- (IBAction)showPageMenu:(id)sender {
[NSMenu popUpContextMenu:pageMenu_
withEvent:[NSApp currentEvent]
forView:pageButton_];
}
- (IBAction)showWrenchMenu:(id)sender {
[NSMenu popUpContextMenu:wrenchMenu_
withEvent:[NSApp currentEvent]
forView:wrenchButton_];
}
- (NSRect)starButtonInWindowCoordinates {
return [[[starButton_ window] contentView] convertRect:[starButton_ bounds]
fromView:starButton_];
}
- (gfx::Rect)autocompletePopupPosition {
// The popup should span from the left edge of the star button to the right
// edge of the go button. The returned height is ignored.
NSRect locationFrame = [locationBar_ frame];
int minX = NSMinX([starButton_ frame]);
int maxX = NSMaxX([goButton_ frame]);
DCHECK(minX < NSMinX(locationFrame));
DCHECK(maxX > NSMaxX(locationFrame));
// TODO(shess): The + 1.0 is because the field's visual boundary
// differs from its on-screen boundary.
NSRect r = NSMakeRect(minX, NSMinY(locationFrame) + 1.0, maxX - minX, 0);
return gfx::Rect(NSRectToCGRect([[self view] convertRect:r toView:nil]));
}
@end
|