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