Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / find_bar / find_bar_cocoa_controller.mm
blob14b691b3e49bdb67be302238d566ccad4124586b
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 <Cocoa/Cocoa.h>
7 #include "base/auto_reset.h"
8 #include "base/mac/bundle_locations.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/browser_finder.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
13 #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
14 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
15 #import "chrome/browser/ui/cocoa/find_bar/find_bar_text_field.h"
16 #import "chrome/browser/ui/cocoa/find_bar/find_bar_text_field_cell.h"
17 #import "chrome/browser/ui/cocoa/image_button_cell.h"
18 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
19 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
20 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "grit/theme_resources.h"
24 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
25 #import "ui/base/cocoa/find_pasteboard.h"
26 #import "ui/base/cocoa/focus_tracker.h"
27 #import "ui/base/cocoa/nsview_additions.h"
28 #include "ui/resources/grit/ui_resources.h"
30 using content::NativeWebKeyboardEvent;
32 const float kFindBarOpenDuration = 0.2;
33 const float kFindBarCloseDuration = 0.15;
34 const float kFindBarMoveDuration = 0.15;
35 const float kRightEdgeOffset = 25;
38 @interface FindBarCocoaController (PrivateMethods) <NSAnimationDelegate>
39 // Returns the appropriate frame for a hidden find bar.
40 - (NSRect)hiddenFindBarFrame;
42 // Animates the given |view| to the given |endFrame| within |duration| seconds.
43 // Returns a new NSViewAnimation.
44 - (NSViewAnimation*)createAnimationForView:(NSView*)view
45                                    toFrame:(NSRect)endFrame
46                                   duration:(float)duration;
48 // Sets the frame of |findBarView_|.  |duration| is ignored if |animate| is NO.
49 - (void)setFindBarFrame:(NSRect)endFrame
50                 animate:(BOOL)animate
51                duration:(float)duration;
53 // Returns the horizontal position the FindBar should use in order to avoid
54 // overlapping with the current find result, if there's one.
55 - (float)findBarHorizontalPosition;
57 // Adjusts the horizontal position if necessary to avoid overlapping with the
58 // current find result.
59 - (void)moveFindBarIfNecessary:(BOOL)animate;
61 // Puts |text| into the find bar and enables the buttons, but doesn't start a
62 // new search for |text|.
63 - (void)prepopulateText:(NSString*)text;
65 // Clears the find results for all tabs in browser associated with this find
66 // bar. If |suppressPboardUpdateActions_| is true then the current tab is not
67 // cleared.
68 - (void)clearFindResultsForCurrentBrowser;
70 - (BrowserWindowController*)browserWindowController;
71 @end
73 @implementation FindBarCocoaController
75 @synthesize findBarView = findBarView_;
77 - (id)initWithBrowser:(Browser*)browser {
78   if ((self = [super initWithNibName:@"FindBar"
79                               bundle:base::mac::FrameworkBundle()])) {
80     [[NSNotificationCenter defaultCenter]
81         addObserver:self
82            selector:@selector(findPboardUpdated:)
83                name:kFindPasteboardChangedNotification
84              object:[FindPasteboard sharedInstance]];
85     browser_ = browser;
86   }
87   return self;
90 - (void)dealloc {
91   // All animations should have been explicitly stopped before a tab is closed.
92   DCHECK(!showHideAnimation_.get());
93   DCHECK(!moveAnimation_.get());
94   [[NSNotificationCenter defaultCenter] removeObserver:self];
95   [super dealloc];
98 - (void)setFindBarBridge:(FindBarBridge*)findBarBridge {
99   DCHECK(!findBarBridge_);  // should only be called once.
100   findBarBridge_ = findBarBridge;
103 - (void)awakeFromNib {
104   [[closeButton_ cell] setImageID:IDR_CLOSE_1
105                    forButtonState:image_button_cell::kDefaultState];
106   [[closeButton_ cell] setImageID:IDR_CLOSE_1_H
107                    forButtonState:image_button_cell::kHoverState];
108   [[closeButton_ cell] setImageID:IDR_CLOSE_1_P
109                    forButtonState:image_button_cell::kPressedState];
110   [[closeButton_ cell] setImageID:IDR_CLOSE_1
111                    forButtonState:image_button_cell::kDisabledState];
113   [findBarView_ setFrame:[self hiddenFindBarFrame]];
114   defaultWidth_ = NSWidth([findBarView_ frame]);
115   [[self view] setHidden:YES];
117   [self prepopulateText:[[FindPasteboard sharedInstance] findText]];
120 - (IBAction)close:(id)sender {
121   if (findBarBridge_)
122     findBarBridge_->GetFindBarController()->EndFindSession(
123         FindBarController::kKeepSelectionOnPage,
124         FindBarController::kKeepResultsInFindBox);
126   // Turn off hover state on close button else the button will remain
127   // hovered when we bring the find bar back up.
128   // crbug.com/227424
129   [[closeButton_ cell] setIsMouseInside:NO];
132 - (IBAction)previousResult:(id)sender {
133   if (findBarBridge_) {
134     FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(
135         findBarBridge_->GetFindBarController()->web_contents());
136     findTabHelper->StartFinding(
137         base::SysNSStringToUTF16([findText_ stringValue]),
138         false, false);
139   }
142 - (IBAction)nextResult:(id)sender {
143   if (findBarBridge_) {
144     FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(
145         findBarBridge_->GetFindBarController()->web_contents());
146     findTabHelper->StartFinding(
147         base::SysNSStringToUTF16([findText_ stringValue]),
148         true, false);
149   }
152 - (void)findPboardUpdated:(NSNotification*)notification {
153   if (!suppressPboardUpdateActions_)
154     [self prepopulateText:[[FindPasteboard sharedInstance] findText]];
155   [self clearFindResultsForCurrentBrowser];
158 - (void)positionFindBarViewAtMaxY:(CGFloat)maxY maxWidth:(CGFloat)maxWidth {
159   NSView* containerView = [self view];
160   CGFloat containerHeight = NSHeight([containerView frame]);
161   CGFloat containerWidth = std::min(maxWidth, defaultWidth_);
163   // Adjust where we'll actually place the find bar.
164   maxY += [containerView cr_lineWidth];
165   maxY_ = maxY;
166   CGFloat x = [self findBarHorizontalPosition];
167   NSRect newFrame = NSMakeRect(x, maxY - containerHeight,
168                                containerWidth, containerHeight);
170   if (moveAnimation_.get() != nil) {
171     NSRect frame = [containerView frame];
172     [moveAnimation_ stopAnimation];
173     // Restore to the X position before the animation was stopped. The Y
174     // position is immediately adjusted.
175     frame.origin.y = newFrame.origin.y;
176     [containerView setFrame:frame];
177     moveAnimation_.reset([self createAnimationForView:containerView
178                                               toFrame:newFrame
179                                              duration:kFindBarMoveDuration]);
180   } else {
181     [containerView setFrame:newFrame];
182   }
185 - (BOOL)isOffTheRecordProfile {
186   return browser_ && browser_->profile() &&
187          browser_->profile()->IsOffTheRecord();
190 // NSControl delegate method.
191 - (void)controlTextDidChange:(NSNotification*)aNotification {
192   if (!findBarBridge_)
193     return;
195   content::WebContents* webContents =
196       findBarBridge_->GetFindBarController()->web_contents();
197   if (!webContents)
198     return;
199   FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(webContents);
201   NSString* findText = [findText_ stringValue];
202   if (![self isOffTheRecordProfile]) {
203     base::AutoReset<BOOL> suppressReset(&suppressPboardUpdateActions_, YES);
204     [[FindPasteboard sharedInstance] setFindText:findText];
205   }
207   if ([findText length] > 0) {
208     findTabHelper->
209         StartFinding(base::SysNSStringToUTF16(findText), true, false);
210   } else {
211     // The textbox is empty so we reset.
212     findTabHelper->StopFinding(FindBarController::kClearSelectionOnPage);
213     [self updateUIForFindResult:findTabHelper->find_result()
214                        withText:base::string16()];
215   }
218 // NSControl delegate method
219 - (BOOL)control:(NSControl*)control
220     textView:(NSTextView*)textView
221     doCommandBySelector:(SEL)command {
222   if (command == @selector(insertNewline:)) {
223     // Pressing Return
224     NSEvent* event = [NSApp currentEvent];
226     if ([event modifierFlags] & NSShiftKeyMask)
227       [previousButton_ performClick:nil];
228     else
229       [nextButton_ performClick:nil];
231     return YES;
232   } else if (command == @selector(insertLineBreak:)) {
233     // Pressing Ctrl-Return
234     if (findBarBridge_) {
235       findBarBridge_->GetFindBarController()->EndFindSession(
236           FindBarController::kActivateSelectionOnPage,
237           FindBarController::kClearResultsInFindBox);
238     }
239     return YES;
240   } else if (command == @selector(pageUp:) ||
241              command == @selector(pageUpAndModifySelection:) ||
242              command == @selector(scrollPageUp:) ||
243              command == @selector(pageDown:) ||
244              command == @selector(pageDownAndModifySelection:) ||
245              command == @selector(scrollPageDown:) ||
246              command == @selector(scrollToBeginningOfDocument:) ||
247              command == @selector(scrollToEndOfDocument:) ||
248              command == @selector(moveUp:) ||
249              command == @selector(moveDown:)) {
250     content::WebContents* web_contents =
251         findBarBridge_->GetFindBarController()->web_contents();
252     if (!web_contents)
253       return NO;
255     // Sanity-check to make sure we got a keyboard event.
256     NSEvent* event = [NSApp currentEvent];
257     if ([event type] != NSKeyDown && [event type] != NSKeyUp)
258       return NO;
260     // Forward the event to the renderer.
261     // TODO(rohitrao): Should this call -[BaseView keyEvent:]?  Is there code in
262     // that function that we want to keep or avoid? Calling
263     // |ForwardKeyboardEvent()| directly ignores edit commands, which breaks
264     // cmd-up/down if we ever decide to include |moveToBeginningOfDocument:| in
265     // the list above.
266     content::RenderViewHost* render_view_host =
267         web_contents->GetRenderViewHost();
268     render_view_host->ForwardKeyboardEvent(NativeWebKeyboardEvent(event));
269     return YES;
270   }
272   return NO;
275 // Methods from FindBar
276 - (void)showFindBar:(BOOL)animate {
277   // Save the currently-focused view.  |findBarView_| is in the view
278   // hierarchy by now.  showFindBar can be called even when the
279   // findbar is already open, so do not overwrite an already saved
280   // view.
281   if (!focusTracker_.get())
282     focusTracker_.reset(
283         [[FocusTracker alloc] initWithWindow:[findBarView_ window]]);
285   // The browser window might have changed while the FindBar was hidden.
286   // Update its position now.
287   [[self browserWindowController] layoutSubviews];
289   // Move to the correct horizontal position first, to prevent the FindBar
290   // from jumping around when switching tabs.
291   // Prevent jumping while the FindBar is animating (hiding, then showing) too.
292   if (![self isFindBarVisible])
293     [self moveFindBarIfNecessary:NO];
295   // Animate the view into place.
296   NSRect frame = [findBarView_ frame];
297   frame.origin = NSZeroPoint;
298   [self setFindBarFrame:frame animate:animate duration:kFindBarOpenDuration];
301 - (void)hideFindBar:(BOOL)animate {
302   NSRect frame = [self hiddenFindBarFrame];
303   [self setFindBarFrame:frame animate:animate duration:kFindBarCloseDuration];
306 - (void)stopAnimation {
307   if (showHideAnimation_.get()) {
308     [showHideAnimation_ stopAnimation];
309     showHideAnimation_.reset(nil);
310   }
311   if (moveAnimation_.get()) {
312     [moveAnimation_ stopAnimation];
313     moveAnimation_.reset(nil);
314   }
317 - (void)setFocusAndSelection {
318   [[findText_ window] makeFirstResponder:findText_];
320   // Enable the buttons if the find text is non-empty.
321   BOOL buttonsEnabled = ([[findText_ stringValue] length] > 0) ? YES : NO;
322   [previousButton_ setEnabled:buttonsEnabled];
323   [nextButton_ setEnabled:buttonsEnabled];
326 - (void)restoreSavedFocus {
327   if (!(focusTracker_.get() &&
328         [focusTracker_ restoreFocusInWindow:[findBarView_ window]])) {
329     // Fall back to giving focus to the tab contents.
330     findBarBridge_->GetFindBarController()->web_contents()->Focus();
331   }
332   focusTracker_.reset(nil);
335 - (void)setFindText:(NSString*)findText
336       selectedRange:(const NSRange&)selectedRange {
337   [findText_ setStringValue:findText];
339   if (![self isOffTheRecordProfile]) {
340     // Make sure the text in the find bar always ends up in the find pasteboard
341     // (and, via notifications, in the other find bars too).
342     base::AutoReset<BOOL> suppressReset(&suppressPboardUpdateActions_, YES);
343     [[FindPasteboard sharedInstance] setFindText:findText];
344   }
346   NSText* editor = [findText_ currentEditor];
347   if (selectedRange.location != NSNotFound)
348     [editor setSelectedRange:selectedRange];
351 - (NSString*)findText {
352   return [findText_ stringValue];
355 - (NSRange)selectedRange {
356   NSText* editor = [findText_ currentEditor];
357   return (editor != nil) ? [editor selectedRange] : NSMakeRange(NSNotFound, 0);
360 - (NSString*)matchCountText {
361   return [[findText_ findBarTextFieldCell] resultsString];
364 - (void)updateFindBarForChangedWebContents {
365   content::WebContents* contents =
366       findBarBridge_->GetFindBarController()->web_contents();
367   if (!contents)
368     return;
369   FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(contents);
371   // If the find UI is visible but the results are cleared then also clear
372   // the results label and update the buttons.
373   if (findTabHelper->find_ui_active() &&
374       findTabHelper->previous_find_text().empty()) {
375     BOOL buttonsEnabled = [[findText_ stringValue] length] > 0 ? YES : NO;
376     [previousButton_ setEnabled:buttonsEnabled];
377     [nextButton_ setEnabled:buttonsEnabled];
378     [[findText_ findBarTextFieldCell] clearResults];
379   }
382 - (void)clearResults:(const FindNotificationDetails&)results {
383   // Just call updateUIForFindResult, which will take care of clearing
384   // the search text and the results label.
385   [self updateUIForFindResult:results withText:base::string16()];
388 - (void)updateUIForFindResult:(const FindNotificationDetails&)result
389                      withText:(const base::string16&)findText {
390   // If we don't have any results and something was passed in, then
391   // that means someone pressed Cmd-G while the Find box was
392   // closed. In that case we need to repopulate the Find box with what
393   // was passed in.
394   if ([[findText_ stringValue] length] == 0 && !findText.empty()) {
395     [findText_ setStringValue:base::SysUTF16ToNSString(findText)];
396     [findText_ selectText:self];
397   }
399   // Make sure Find Next and Find Previous are enabled if we found any matches.
400   BOOL buttonsEnabled = result.number_of_matches() > 0;
401   [previousButton_ setEnabled:buttonsEnabled];
402   [nextButton_ setEnabled:buttonsEnabled];
404   // Update the results label.
405   BOOL validRange = result.active_match_ordinal() != -1 &&
406                     result.number_of_matches() != -1;
407   NSString* searchString = [findText_ stringValue];
408   if ([searchString length] > 0 && validRange) {
409     [[findText_ findBarTextFieldCell]
410         setActiveMatch:result.active_match_ordinal()
411                     of:result.number_of_matches()];
412   } else {
413     // If there was no text entered, we don't show anything in the results area.
414     [[findText_ findBarTextFieldCell] clearResults];
415   }
417   [findText_ resetFieldEditorFrameIfNeeded];
419   // If we found any results, reset the focus tracker, so we always
420   // restore focus to the tab contents.
421   if (result.number_of_matches() > 0)
422     focusTracker_.reset(nil);
424   // Adjust the FindBar position, even when there are no matches (so that it
425   // goes back to the default position, if required).
426   [self moveFindBarIfNecessary:[self isFindBarVisible]];
429 - (BOOL)isFindBarVisible {
430   // Find bar is visible if any part of it is on the screen.
431   return NSIntersectsRect([[self view] bounds], [findBarView_ frame]);
434 - (BOOL)isFindBarAnimating {
435   return (showHideAnimation_.get() != nil) || (moveAnimation_.get() != nil);
438 // NSAnimation delegate methods.
439 - (void)animationDidEnd:(NSAnimation*)animation {
440   // Autorelease the animations (cannot use release because the animation object
441   // is still on the stack.
442   if (animation == showHideAnimation_.get()) {
443     [showHideAnimation_.release() autorelease];
444   } else if (animation == moveAnimation_.get()) {
445     [moveAnimation_.release() autorelease];
446   } else {
447     NOTREACHED();
448   }
450   // If the find bar is not visible, make it actually hidden, so it'll no longer
451   // respond to key events.
452   [[self view] setHidden:![self isFindBarVisible]];
455 - (gfx::Point)findBarWindowPosition {
456   gfx::Rect viewRect(NSRectToCGRect([[self view] frame]));
457   // Convert Cocoa coordinates (Y growing up) to Y growing down.
458   // Offset from |maxY_|, which represents the content view's top, instead
459   // of from the superview, which represents the whole browser window.
460   viewRect.set_y(maxY_ - viewRect.bottom());
461   return viewRect.origin();
464 - (int)findBarWidth {
465   return NSWidth([[self view] frame]);
468 @end
470 @implementation FindBarCocoaController (PrivateMethods)
472 - (NSRect)hiddenFindBarFrame {
473   NSRect frame = [findBarView_ frame];
474   NSRect containerBounds = [[self view] bounds];
475   frame.origin = NSMakePoint(NSMinX(containerBounds), NSMaxY(containerBounds));
476   return frame;
479 - (NSViewAnimation*)createAnimationForView:(NSView*)view
480                                    toFrame:(NSRect)endFrame
481                                   duration:(float)duration {
482   NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
483       view, NSViewAnimationTargetKey,
484       [NSValue valueWithRect:endFrame], NSViewAnimationEndFrameKey, nil];
486   NSViewAnimation* animation =
487       [[NSViewAnimation alloc]
488         initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
489   [animation gtm_setDuration:duration
490                    eventMask:NSLeftMouseUpMask];
491   [animation setDelegate:self];
492   [animation startAnimation];
493   return animation;
496 - (void)setFindBarFrame:(NSRect)endFrame
497                 animate:(BOOL)animate
498                duration:(float)duration {
499   // Save the current frame.
500   NSRect startFrame = [findBarView_ frame];
502   // Stop any existing animations.
503   [showHideAnimation_ stopAnimation];
505   if (!animate) {
506     [findBarView_ setFrame:endFrame];
507     [[self view] setHidden:![self isFindBarVisible]];
508     showHideAnimation_.reset(nil);
509     return;
510   }
512   // If animating, ensure that the find bar is not hidden. Hidden status will be
513   // updated at the end of the animation.
514   [[self view] setHidden:NO];
516   // Reset the frame to what was saved above.
517   [findBarView_ setFrame:startFrame];
519   showHideAnimation_.reset([self createAnimationForView:findBarView_
520                                                 toFrame:endFrame
521                                                duration:duration]);
524 - (float)findBarHorizontalPosition {
525   // Get the rect of the FindBar.
526   NSView* view = [self view];
527   NSRect frame = [view frame];
528   gfx::Rect viewRect(NSRectToCGRect(frame));
530   if (!findBarBridge_ || !findBarBridge_->GetFindBarController())
531     return frame.origin.x;
532   content::WebContents* contents =
533       findBarBridge_->GetFindBarController()->web_contents();
534   if (!contents)
535     return frame.origin.x;
537   // Get the size of the container.
538   gfx::Rect containerRect(contents->GetContainerBounds().size());
540   // Position the FindBar on the top right corner.
541   viewRect.set_x(
542       containerRect.width() - viewRect.width() - kRightEdgeOffset);
543   // Convert from Cocoa coordinates (Y growing up) to Y growing down.
544   // Notice that the view frame's Y offset is relative to the whole window,
545   // while GetLocationForFindbarView() expects it relative to the
546   // content's boundaries. |maxY_| has the correct placement in Cocoa coords,
547   // so we just have to invert the Y coordinate.
548   viewRect.set_y(maxY_ - viewRect.bottom());
550   // Get the rect of the current find result, if there is one.
551   const FindNotificationDetails& findResult =
552       FindTabHelper::FromWebContents(contents)->find_result();
553   if (findResult.number_of_matches() == 0)
554     return viewRect.x();
555   gfx::Rect selectionRect(findResult.selection_rect());
557   // Adjust |view_rect| to avoid the |selection_rect| within |container_rect|.
558   gfx::Rect newPos = FindBarController::GetLocationForFindbarView(
559       viewRect, containerRect, selectionRect);
561   return newPos.x();
564 - (void)moveFindBarIfNecessary:(BOOL)animate {
565   // Don't animate during tests.
566   if (FindBarBridge::disable_animations_during_testing_)
567     animate = NO;
569   NSView* view = [self view];
570   NSRect frame = [view frame];
571   float x = [self findBarHorizontalPosition];
572   if (frame.origin.x == x)
573     return;
575   if (animate) {
576     [moveAnimation_ stopAnimation];
577     // Restore to the position before the animation was stopped.
578     [view setFrame:frame];
579     frame.origin.x = x;
580     moveAnimation_.reset([self createAnimationForView:view
581                                               toFrame:frame
582                                              duration:kFindBarMoveDuration]);
583   } else {
584     frame.origin.x = x;
585     [view setFrame:frame];
586   }
589 - (void)prepopulateText:(NSString*)text {
590   [self setFindText:text selectedRange:NSMakeRange(NSNotFound, 0)];
592   // Has to happen after |ClearResults()| above.
593   BOOL buttonsEnabled = [text length] > 0 ? YES : NO;
594   [previousButton_ setEnabled:buttonsEnabled];
595   [nextButton_ setEnabled:buttonsEnabled];
598 - (void)clearFindResultsForCurrentBrowser {
599   if (!browser_)
600     return;
602   content::WebContents* activeWebContents =
603       findBarBridge_->GetFindBarController()->web_contents();
605   TabStripModel* tabStripModel = browser_->tab_strip_model();
606   for (int i = 0; i < tabStripModel->count(); ++i) {
607     content::WebContents* webContents = tabStripModel->GetWebContentsAt(i);
608     if (suppressPboardUpdateActions_ && activeWebContents == webContents)
609       continue;
610     FindTabHelper* findTabHelper =
611         FindTabHelper::FromWebContents(webContents);
612     findTabHelper->StopFinding(FindBarController::kClearSelectionOnPage);
613     findBarBridge_->ClearResults(findTabHelper->find_result());
614   }
617 - (BrowserWindowController*)browserWindowController {
618   if (!browser_)
619     return nil;
620   return [BrowserWindowController
621       browserWindowControllerForWindow:browser_->window()->GetNativeWindow()];
624 @end