1// Copyright (c) 2010 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 "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_hover_state.h" 6#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" 7 8@interface BookmarkBarFolderHoverState(Private) 9- (void)setHoverState:(HoverState)state; 10- (void)closeBookmarkFolderOnHoverButton:(BookmarkButton*)button; 11- (void)openBookmarkFolderOnHoverButton:(BookmarkButton*)button; 12@end 13 14@implementation BookmarkBarFolderHoverState 15 16- (id)init { 17 if ((self = [super init])) { 18 hoverState_ = kHoverStateClosed; 19 } 20 return self; 21} 22 23- (NSDragOperation)draggingEnteredButton:(BookmarkButton*)button { 24 if ([button isFolder]) { 25 if (hoverButton_ == button) { 26 // CASE A: hoverButton_ == button implies we've dragged over 27 // the same folder so no need to open or close anything new. 28 } else if (hoverButton_ && 29 hoverButton_ != button) { 30 // CASE B: we have a hoverButton_ but it is different from the new button. 31 // This implies we've dragged over a new folder, so we'll close the old 32 // and open the new. 33 // Note that we only schedule the open or close if we have no other tasks 34 // currently pending. 35 36 if (hoverState_ == kHoverStateOpen) { 37 // Close the old. 38 [self scheduleCloseBookmarkFolderOnHoverButton]; 39 } else if (hoverState_ == kHoverStateClosed) { 40 // Open the new. 41 [self scheduleOpenBookmarkFolderOnHoverButton:button]; 42 } 43 } else if (!hoverButton_) { 44 // CASE C: we don't have a current hoverButton_ but we have dragged onto 45 // a new folder so we open the new one. 46 [self scheduleOpenBookmarkFolderOnHoverButton:button]; 47 } 48 } else if (!button) { 49 if (hoverButton_) { 50 // CASE D: We have a hoverButton_ but we've moved onto an area that 51 // requires no hover. We close the hoverButton_ in this case. This 52 // means cancelling if the open is pending (i.e. |kHoverStateOpening|) 53 // or closing if we don't alrealy have once in progress. 54 55 // Intiate close only if we have not already done so. 56 if (hoverState_ == kHoverStateOpening) { 57 // Cancel the pending open. 58 [self cancelPendingOpenBookmarkFolderOnHoverButton]; 59 } else if (hoverState_ != kHoverStateClosing) { 60 // Schedule the close. 61 [self scheduleCloseBookmarkFolderOnHoverButton]; 62 } 63 } else { 64 // CASE E: We have neither a hoverButton_ nor a new button that requires 65 // a hover. In this case we do nothing. 66 } 67 } 68 69 return NSDragOperationMove; 70} 71 72- (void)draggingExited { 73 if (hoverButton_) { 74 if (hoverState_ == kHoverStateOpening) { 75 [self cancelPendingOpenBookmarkFolderOnHoverButton]; 76 } else if (hoverState_ == kHoverStateClosing) { 77 [self cancelPendingCloseBookmarkFolderOnHoverButton]; 78 } 79 } 80} 81 82// Schedule close of hover button. Transition to kHoverStateClosing state. 83- (void)scheduleCloseBookmarkFolderOnHoverButton { 84 DCHECK(hoverButton_); 85 [self setHoverState:kHoverStateClosing]; 86 [self performSelector:@selector(closeBookmarkFolderOnHoverButton:) 87 withObject:hoverButton_ 88 afterDelay:bookmarks::kDragHoverCloseDelay 89 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 90} 91 92// Cancel pending hover close. Transition to kHoverStateOpen state. 93- (void)cancelPendingCloseBookmarkFolderOnHoverButton { 94 [self setHoverState:kHoverStateOpen]; 95 [NSObject 96 cancelPreviousPerformRequestsWithTarget:self 97 selector:@selector(closeBookmarkFolderOnHoverButton:) 98 object:hoverButton_]; 99} 100 101// Schedule open of hover button. Transition to kHoverStateOpening state. 102- (void)scheduleOpenBookmarkFolderOnHoverButton:(BookmarkButton*)button { 103 DCHECK(button); 104 hoverButton_.reset([button retain]); 105 [self setHoverState:kHoverStateOpening]; 106 [self performSelector:@selector(openBookmarkFolderOnHoverButton:) 107 withObject:hoverButton_ 108 afterDelay:bookmarks::kDragHoverOpenDelay 109 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 110} 111 112// Cancel pending hover open. Transition to kHoverStateClosed state. 113- (void)cancelPendingOpenBookmarkFolderOnHoverButton { 114 [self setHoverState:kHoverStateClosed]; 115 [NSObject 116 cancelPreviousPerformRequestsWithTarget:self 117 selector:@selector(openBookmarkFolderOnHoverButton:) 118 object:hoverButton_]; 119 hoverButton_.reset(); 120} 121 122// Hover button accessor. For testing only. 123- (BookmarkButton*)hoverButton { 124 return hoverButton_; 125} 126 127// Hover state accessor. For testing only. 128- (HoverState)hoverState { 129 return hoverState_; 130} 131 132// This method encodes the rules of our |hoverButton_| state machine. Only 133// specific state transitions are allowable (encoded in the DCHECK). 134// Note that there is no state for simultaneously opening and closing. A 135// pending open must complete before scheduling a close, and vice versa. And 136// it is not possible to make a transition directly from open to closed, and 137// vice versa. 138- (void)setHoverState:(HoverState)state { 139 DCHECK( 140 (hoverState_ == kHoverStateClosed && state == kHoverStateOpening) || 141 (hoverState_ == kHoverStateOpening && state == kHoverStateClosed) || 142 (hoverState_ == kHoverStateOpening && state == kHoverStateOpen) || 143 (hoverState_ == kHoverStateOpen && state == kHoverStateClosing) || 144 (hoverState_ == kHoverStateClosing && state == kHoverStateOpen) || 145 (hoverState_ == kHoverStateClosing && state == kHoverStateClosed) 146 ) << "bad transition: old = " << hoverState_ << " new = " << state; 147 148 hoverState_ = state; 149} 150 151// Called after a delay to close a previously hover-opened folder. 152// Note: this method is not meant to be invoked directly, only through 153// a delayed call to |scheduleCloseBookmarkFolderOnHoverButton:|. 154- (void)closeBookmarkFolderOnHoverButton:(BookmarkButton*)button { 155 [NSObject 156 cancelPreviousPerformRequestsWithTarget:self 157 selector:@selector(closeBookmarkFolderOnHoverButton:) 158 object:hoverButton_]; 159 [self setHoverState:kHoverStateClosed]; 160 [[button target] closeBookmarkFolder:button]; 161 hoverButton_.reset(); 162} 163 164// Called after a delay to open a new hover folder. 165// Note: this method is not meant to be invoked directly, only through 166// a delayed call to |scheduleOpenBookmarkFolderOnHoverButton:|. 167- (void)openBookmarkFolderOnHoverButton:(BookmarkButton*)button { 168 [self setHoverState:kHoverStateOpen]; 169 [[button target] performSelector:@selector(openBookmarkFolderFromButton:) 170 withObject:button]; 171} 172 173@end 174