OLD | NEW |
| (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 <Cocoa/Cocoa.h> | |
6 | |
7 #include "base/mac/mac_util.h" | |
8 #include "base/sys_string_conversions.h" | |
9 #include "chrome/browser/renderer_host/render_view_host.h" | |
10 #include "chrome/browser/tab_contents/tab_contents.h" | |
11 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h" | |
12 #import "chrome/browser/ui/cocoa/find_bar_cocoa_controller.h" | |
13 #import "chrome/browser/ui/cocoa/find_bar_bridge.h" | |
14 #import "chrome/browser/ui/cocoa/find_bar_text_field.h" | |
15 #import "chrome/browser/ui/cocoa/find_bar_text_field_cell.h" | |
16 #import "chrome/browser/ui/cocoa/find_pasteboard.h" | |
17 #import "chrome/browser/ui/cocoa/focus_tracker.h" | |
18 #import "chrome/browser/ui/cocoa/tab_strip_controller.h" | |
19 #include "chrome/browser/ui/find_bar/find_bar_controller.h" | |
20 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" | |
21 | |
22 namespace { | |
23 const float kFindBarOpenDuration = 0.2; | |
24 const float kFindBarCloseDuration = 0.15; | |
25 } | |
26 | |
27 @interface FindBarCocoaController (PrivateMethods) | |
28 // Returns the appropriate frame for a hidden find bar. | |
29 - (NSRect)hiddenFindBarFrame; | |
30 | |
31 // Sets the frame of |findBarView_|. |duration| is ignored if |animate| is NO. | |
32 - (void)setFindBarFrame:(NSRect)endFrame | |
33 animate:(BOOL)animate | |
34 duration:(float)duration; | |
35 | |
36 // Optionally stops the current search, puts |text| into the find bar, and | |
37 // enables the buttons, but doesn't start a new search for |text|. | |
38 - (void)prepopulateText:(NSString*)text stopSearch:(BOOL)stopSearch; | |
39 @end | |
40 | |
41 @implementation FindBarCocoaController | |
42 | |
43 - (id)init { | |
44 if ((self = [super initWithNibName:@"FindBar" | |
45 bundle:base::mac::MainAppBundle()])) { | |
46 [[NSNotificationCenter defaultCenter] | |
47 addObserver:self | |
48 selector:@selector(findPboardUpdated:) | |
49 name:kFindPasteboardChangedNotification | |
50 object:[FindPasteboard sharedInstance]]; | |
51 } | |
52 return self; | |
53 } | |
54 | |
55 - (void)dealloc { | |
56 // All animations should be explicitly stopped by the TabContents before a tab | |
57 // is closed. | |
58 DCHECK(!currentAnimation_.get()); | |
59 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
60 [super dealloc]; | |
61 } | |
62 | |
63 - (void)setFindBarBridge:(FindBarBridge*)findBarBridge { | |
64 DCHECK(!findBarBridge_); // should only be called once. | |
65 findBarBridge_ = findBarBridge; | |
66 } | |
67 | |
68 - (void)awakeFromNib { | |
69 [findBarView_ setFrame:[self hiddenFindBarFrame]]; | |
70 | |
71 // Stopping the search requires a findbar controller, which isn't valid yet | |
72 // during setup. Furthermore, there is no active search yet anyway. | |
73 [self prepopulateText:[[FindPasteboard sharedInstance] findText] | |
74 stopSearch:NO]; | |
75 } | |
76 | |
77 - (IBAction)close:(id)sender { | |
78 if (findBarBridge_) | |
79 findBarBridge_->GetFindBarController()->EndFindSession( | |
80 FindBarController::kKeepSelection); | |
81 } | |
82 | |
83 - (IBAction)previousResult:(id)sender { | |
84 if (findBarBridge_) | |
85 findBarBridge_->GetFindBarController()->tab_contents()->StartFinding( | |
86 base::SysNSStringToUTF16([findText_ stringValue]), | |
87 false, false); | |
88 } | |
89 | |
90 - (IBAction)nextResult:(id)sender { | |
91 if (findBarBridge_) | |
92 findBarBridge_->GetFindBarController()->tab_contents()->StartFinding( | |
93 base::SysNSStringToUTF16([findText_ stringValue]), | |
94 true, false); | |
95 } | |
96 | |
97 - (void)findPboardUpdated:(NSNotification*)notification { | |
98 if (suppressPboardUpdateActions_) | |
99 return; | |
100 [self prepopulateText:[[FindPasteboard sharedInstance] findText] | |
101 stopSearch:YES]; | |
102 } | |
103 | |
104 - (void)positionFindBarViewAtMaxY:(CGFloat)maxY maxWidth:(CGFloat)maxWidth { | |
105 static const CGFloat kRightEdgeOffset = 25; | |
106 NSView* containerView = [self view]; | |
107 CGFloat containerHeight = NSHeight([containerView frame]); | |
108 CGFloat containerWidth = NSWidth([containerView frame]); | |
109 | |
110 // Adjust where we'll actually place the find bar. | |
111 CGFloat maxX = maxWidth - kRightEdgeOffset; | |
112 DLOG_IF(WARNING, maxX < 0) << "Window too narrow for find bar"; | |
113 maxY += 1; | |
114 | |
115 NSRect newFrame = NSMakeRect(maxX - containerWidth, maxY - containerHeight, | |
116 containerWidth, containerHeight); | |
117 [containerView setFrame:newFrame]; | |
118 } | |
119 | |
120 // NSControl delegate method. | |
121 - (void)controlTextDidChange:(NSNotification *)aNotification { | |
122 if (!findBarBridge_) | |
123 return; | |
124 | |
125 TabContents* tab_contents = | |
126 findBarBridge_->GetFindBarController()->tab_contents(); | |
127 if (!tab_contents) | |
128 return; | |
129 | |
130 NSString* findText = [findText_ stringValue]; | |
131 suppressPboardUpdateActions_ = YES; | |
132 [[FindPasteboard sharedInstance] setFindText:findText]; | |
133 suppressPboardUpdateActions_ = NO; | |
134 | |
135 if ([findText length] > 0) { | |
136 tab_contents->StartFinding(base::SysNSStringToUTF16(findText), true, false); | |
137 } else { | |
138 // The textbox is empty so we reset. | |
139 tab_contents->StopFinding(FindBarController::kClearSelection); | |
140 [self updateUIForFindResult:tab_contents->find_result() | |
141 withText:string16()]; | |
142 } | |
143 } | |
144 | |
145 // NSControl delegate method | |
146 - (BOOL)control:(NSControl*)control | |
147 textView:(NSTextView*)textView | |
148 doCommandBySelector:(SEL)command { | |
149 if (command == @selector(insertNewline:)) { | |
150 // Pressing Return | |
151 NSEvent* event = [NSApp currentEvent]; | |
152 | |
153 if ([event modifierFlags] & NSShiftKeyMask) | |
154 [previousButton_ performClick:nil]; | |
155 else | |
156 [nextButton_ performClick:nil]; | |
157 | |
158 return YES; | |
159 } else if (command == @selector(insertLineBreak:)) { | |
160 // Pressing Ctrl-Return | |
161 if (findBarBridge_) { | |
162 findBarBridge_->GetFindBarController()->EndFindSession( | |
163 FindBarController::kActivateSelection); | |
164 } | |
165 return YES; | |
166 } else if (command == @selector(pageUp:) || | |
167 command == @selector(pageUpAndModifySelection:) || | |
168 command == @selector(scrollPageUp:) || | |
169 command == @selector(pageDown:) || | |
170 command == @selector(pageDownAndModifySelection:) || | |
171 command == @selector(scrollPageDown:) || | |
172 command == @selector(scrollToBeginningOfDocument:) || | |
173 command == @selector(scrollToEndOfDocument:) || | |
174 command == @selector(moveUp:) || | |
175 command == @selector(moveDown:)) { | |
176 TabContents* contents = | |
177 findBarBridge_->GetFindBarController()->tab_contents(); | |
178 if (!contents) | |
179 return NO; | |
180 | |
181 // Sanity-check to make sure we got a keyboard event. | |
182 NSEvent* event = [NSApp currentEvent]; | |
183 if ([event type] != NSKeyDown && [event type] != NSKeyUp) | |
184 return NO; | |
185 | |
186 // Forward the event to the renderer. | |
187 // TODO(rohitrao): Should this call -[BaseView keyEvent:]? Is there code in | |
188 // that function that we want to keep or avoid? Calling | |
189 // |ForwardKeyboardEvent()| directly ignores edit commands, which breaks | |
190 // cmd-up/down if we ever decide to include |moveToBeginningOfDocument:| in | |
191 // the list above. | |
192 RenderViewHost* render_view_host = contents->render_view_host(); | |
193 render_view_host->ForwardKeyboardEvent(NativeWebKeyboardEvent(event)); | |
194 return YES; | |
195 } | |
196 | |
197 return NO; | |
198 } | |
199 | |
200 // Methods from FindBar | |
201 - (void)showFindBar:(BOOL)animate { | |
202 // Save the currently-focused view. |findBarView_| is in the view | |
203 // hierarchy by now. showFindBar can be called even when the | |
204 // findbar is already open, so do not overwrite an already saved | |
205 // view. | |
206 if (!focusTracker_.get()) | |
207 focusTracker_.reset( | |
208 [[FocusTracker alloc] initWithWindow:[findBarView_ window]]); | |
209 | |
210 // Animate the view into place. | |
211 NSRect frame = [findBarView_ frame]; | |
212 frame.origin = NSMakePoint(0, 0); | |
213 [self setFindBarFrame:frame animate:animate duration:kFindBarOpenDuration]; | |
214 } | |
215 | |
216 - (void)hideFindBar:(BOOL)animate { | |
217 NSRect frame = [self hiddenFindBarFrame]; | |
218 [self setFindBarFrame:frame animate:animate duration:kFindBarCloseDuration]; | |
219 } | |
220 | |
221 - (void)stopAnimation { | |
222 if (currentAnimation_.get()) { | |
223 [currentAnimation_ stopAnimation]; | |
224 currentAnimation_.reset(nil); | |
225 } | |
226 } | |
227 | |
228 - (void)setFocusAndSelection { | |
229 [[findText_ window] makeFirstResponder:findText_]; | |
230 | |
231 // Enable the buttons if the find text is non-empty. | |
232 BOOL buttonsEnabled = ([[findText_ stringValue] length] > 0) ? YES : NO; | |
233 [previousButton_ setEnabled:buttonsEnabled]; | |
234 [nextButton_ setEnabled:buttonsEnabled]; | |
235 } | |
236 | |
237 - (void)restoreSavedFocus { | |
238 if (!(focusTracker_.get() && | |
239 [focusTracker_ restoreFocusInWindow:[findBarView_ window]])) { | |
240 // Fall back to giving focus to the tab contents. | |
241 findBarBridge_->GetFindBarController()->tab_contents()->Focus(); | |
242 } | |
243 focusTracker_.reset(nil); | |
244 } | |
245 | |
246 - (void)setFindText:(NSString*)findText { | |
247 [findText_ setStringValue:findText]; | |
248 | |
249 // Make sure the text in the find bar always ends up in the find pasteboard | |
250 // (and, via notifications, in the other find bars too). | |
251 [[FindPasteboard sharedInstance] setFindText:findText]; | |
252 } | |
253 | |
254 - (void)clearResults:(const FindNotificationDetails&)results { | |
255 // Just call updateUIForFindResult, which will take care of clearing | |
256 // the search text and the results label. | |
257 [self updateUIForFindResult:results withText:string16()]; | |
258 } | |
259 | |
260 - (void)updateUIForFindResult:(const FindNotificationDetails&)result | |
261 withText:(const string16&)findText { | |
262 // If we don't have any results and something was passed in, then | |
263 // that means someone pressed Cmd-G while the Find box was | |
264 // closed. In that case we need to repopulate the Find box with what | |
265 // was passed in. | |
266 if ([[findText_ stringValue] length] == 0 && !findText.empty()) { | |
267 [findText_ setStringValue:base::SysUTF16ToNSString(findText)]; | |
268 [findText_ selectText:self]; | |
269 } | |
270 | |
271 // Make sure Find Next and Find Previous are enabled if we found any matches. | |
272 BOOL buttonsEnabled = result.number_of_matches() > 0 ? YES : NO; | |
273 [previousButton_ setEnabled:buttonsEnabled]; | |
274 [nextButton_ setEnabled:buttonsEnabled]; | |
275 | |
276 // Update the results label. | |
277 BOOL validRange = result.active_match_ordinal() != -1 && | |
278 result.number_of_matches() != -1; | |
279 NSString* searchString = [findText_ stringValue]; | |
280 if ([searchString length] > 0 && validRange) { | |
281 [[findText_ findBarTextFieldCell] | |
282 setActiveMatch:result.active_match_ordinal() | |
283 of:result.number_of_matches()]; | |
284 } else { | |
285 // If there was no text entered, we don't show anything in the results area. | |
286 [[findText_ findBarTextFieldCell] clearResults]; | |
287 } | |
288 | |
289 [findText_ resetFieldEditorFrameIfNeeded]; | |
290 | |
291 // If we found any results, reset the focus tracker, so we always | |
292 // restore focus to the tab contents. | |
293 if (result.number_of_matches() > 0) | |
294 focusTracker_.reset(nil); | |
295 } | |
296 | |
297 - (BOOL)isFindBarVisible { | |
298 // Find bar is visible if any part of it is on the screen. | |
299 return NSIntersectsRect([[self view] bounds], [findBarView_ frame]); | |
300 } | |
301 | |
302 - (BOOL)isFindBarAnimating { | |
303 return (currentAnimation_.get() != nil); | |
304 } | |
305 | |
306 // NSAnimation delegate methods. | |
307 - (void)animationDidEnd:(NSAnimation*)animation { | |
308 // Autorelease the animation (cannot use release because the animation object | |
309 // is still on the stack. | |
310 DCHECK(animation == currentAnimation_.get()); | |
311 [currentAnimation_.release() autorelease]; | |
312 | |
313 // If the find bar is not visible, make it actually hidden, so it'll no longer | |
314 // respond to key events. | |
315 [findBarView_ setHidden:![self isFindBarVisible]]; | |
316 } | |
317 | |
318 @end | |
319 | |
320 @implementation FindBarCocoaController (PrivateMethods) | |
321 | |
322 - (NSRect)hiddenFindBarFrame { | |
323 NSRect frame = [findBarView_ frame]; | |
324 NSRect containerBounds = [[self view] bounds]; | |
325 frame.origin = NSMakePoint(NSMinX(containerBounds), NSMaxY(containerBounds)); | |
326 return frame; | |
327 } | |
328 | |
329 - (void)setFindBarFrame:(NSRect)endFrame | |
330 animate:(BOOL)animate | |
331 duration:(float)duration { | |
332 // Save the current frame. | |
333 NSRect startFrame = [findBarView_ frame]; | |
334 | |
335 // Stop any existing animations. | |
336 [currentAnimation_ stopAnimation]; | |
337 | |
338 if (!animate) { | |
339 [findBarView_ setFrame:endFrame]; | |
340 [findBarView_ setHidden:![self isFindBarVisible]]; | |
341 currentAnimation_.reset(nil); | |
342 return; | |
343 } | |
344 | |
345 // If animating, ensure that the find bar is not hidden. Hidden status will be | |
346 // updated at the end of the animation. | |
347 [findBarView_ setHidden:NO]; | |
348 | |
349 // Reset the frame to what was saved above. | |
350 [findBarView_ setFrame:startFrame]; | |
351 NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: | |
352 findBarView_, NSViewAnimationTargetKey, | |
353 [NSValue valueWithRect:endFrame], NSViewAnimationEndFrameKey, nil]; | |
354 | |
355 currentAnimation_.reset( | |
356 [[NSViewAnimation alloc] | |
357 initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]]); | |
358 [currentAnimation_ gtm_setDuration:duration | |
359 eventMask:NSLeftMouseUpMask]; | |
360 [currentAnimation_ setDelegate:self]; | |
361 [currentAnimation_ startAnimation]; | |
362 } | |
363 | |
364 - (void)prepopulateText:(NSString*)text stopSearch:(BOOL)stopSearch{ | |
365 [self setFindText:text]; | |
366 | |
367 // End the find session, hide the "x of y" text and disable the | |
368 // buttons, but do not close the find bar or raise the window here. | |
369 if (stopSearch && findBarBridge_) { | |
370 TabContents* contents = | |
371 findBarBridge_->GetFindBarController()->tab_contents(); | |
372 if (contents) { | |
373 contents->StopFinding(FindBarController::kClearSelection); | |
374 findBarBridge_->ClearResults(contents->find_result()); | |
375 } | |
376 } | |
377 | |
378 // Has to happen after |ClearResults()| above. | |
379 BOOL buttonsEnabled = [text length] > 0 ? YES : NO; | |
380 [previousButton_ setEnabled:buttonsEnabled]; | |
381 [nextButton_ setEnabled:buttonsEnabled]; | |
382 } | |
383 | |
384 @end | |
OLD | NEW |