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