• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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