Home | History | Annotate | Download | only in bookmarks
      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