IDEA-80459 allow drag to finish before detaching drag helper
[idea/community.git] / platform / platform-api / src / com / intellij / ui / MouseDragHelper.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ui;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.ui.NullableComponent;
20 import com.intellij.openapi.util.Disposer;
21 import com.intellij.openapi.wm.IdeGlassPane;
22 import com.intellij.openapi.wm.IdeGlassPaneUtil;
23 import com.intellij.ui.awt.RelativePoint;
24 import com.intellij.util.ui.update.Activatable;
25 import com.intellij.util.ui.update.UiNotifyConnector;
26
27 import javax.swing.*;
28 import java.awt.*;
29 import java.awt.event.MouseEvent;
30 import java.awt.event.MouseListener;
31 import java.awt.event.MouseMotionListener;
32
33 public abstract class MouseDragHelper implements MouseListener, MouseMotionListener {
34
35   public static final int DRAG_START_DEADZONE = 7;
36
37   private final JComponent myDragComponent;
38
39   private Point myPressPointScreen;
40   private Point myPressPointComponent;
41
42   private boolean myDraggingNow;
43   private boolean myDragJustStarted;
44   private IdeGlassPane myGlassPane;
45   private final Disposable myParentDisposable;
46   private Dimension myDelta;
47
48   private boolean myDetachPostponed;
49   private boolean myDetachingMode;
50
51   public MouseDragHelper(Disposable parent, final JComponent dragComponent) {
52     myDragComponent = dragComponent;
53     myParentDisposable = parent;
54
55   }
56
57   public void start() {
58     if (myGlassPane != null) return;
59
60     new UiNotifyConnector(myDragComponent, new Activatable() {
61       public void showNotify() {
62         attach();
63       }
64
65       public void hideNotify() {
66         detach(true);
67       }
68     });
69
70     Disposer.register(myParentDisposable, new Disposable() {
71       public void dispose() {
72         stop();
73       }
74     });
75   }
76
77   private void attach() {
78     if (myDetachPostponed) {
79       myDetachPostponed = false;
80       return;
81     }
82     myGlassPane = IdeGlassPaneUtil.find(myDragComponent);
83     myGlassPane.addMousePreprocessor(this, myParentDisposable);
84     myGlassPane.addMouseMotionPreprocessor(this, myParentDisposable);
85   }
86
87   public void stop() {
88     detach(false);
89   }
90
91   private void detach(boolean canPostponeDetach) {
92     if (canPostponeDetach && myDraggingNow) {
93       myDetachPostponed = true;
94       return;
95     }
96     if (myGlassPane != null) {
97       myGlassPane.removeMousePreprocessor(this);
98       myGlassPane.removeMouseMotionPreprocessor(this);
99       myGlassPane = null;
100     }
101   }
102
103   public void mousePressed(final MouseEvent e) {
104     if (!canStartDragging(e)) return;
105
106     myPressPointScreen = new RelativePoint(e).getScreenPoint();
107     myPressPointComponent = e.getPoint();
108
109     myDelta = new Dimension();
110     if (myDragComponent.isShowing()) {
111       final Point delta = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), myDragComponent);
112       myDelta.width = delta.x;
113       myDelta.height = delta.y;
114     }
115   }
116
117   public void mouseReleased(final MouseEvent e) {
118     boolean wasDragging = myDraggingNow;
119     myPressPointScreen = null;
120     myDraggingNow = false;
121     myDragJustStarted = false;
122
123     if (wasDragging) {
124       try {
125         if (myDetachingMode) {
126           processDragOutFinish(e);
127         } else {
128           processDragFinish(e, false);
129         }
130       }
131       finally {
132         myDraggingNow = false;
133         myPressPointComponent = null;
134         myPressPointScreen = null;
135         myDetachingMode = false;
136         e.consume();
137         if (myDetachPostponed) {
138           myDetachPostponed = false;
139           detach(false);
140         }
141       }
142     }
143   }
144
145   public void mouseDragged(final MouseEvent e) {
146     if (myPressPointScreen == null) return;
147
148     final boolean deadZone = isWithinDeadZone(e);
149     if (!myDraggingNow && !deadZone) {
150       myDraggingNow = true;
151       myDragJustStarted = true;
152     }
153     else if (myDraggingNow) {
154       myDragJustStarted = false;
155     }
156
157     if (myDraggingNow && myPressPointScreen != null) {
158       final Point draggedTo = new RelativePoint(e).getScreenPoint();
159
160       boolean dragOutStarted = false;
161       if (!myDetachingMode) {
162         if (isDragOut(e, draggedTo, (Point)myPressPointScreen.clone())) {
163           myDetachingMode = true;
164           processDragFinish(e, true);
165           dragOutStarted = true;
166         }
167       }
168
169       if (myDetachingMode) {
170         processDragOut(e, draggedTo, (Point)myPressPointScreen.clone(), dragOutStarted);
171       } else {
172         processDrag(e, draggedTo, (Point)myPressPointScreen.clone());
173       }
174
175       e.consume();
176     }
177   }
178
179   private boolean canStartDragging(MouseEvent me) {
180     if (me.getButton() != MouseEvent.BUTTON1) return false;
181     if (!myDragComponent.isShowing()) return false;
182
183     Component component = me.getComponent();
184     if (NullableComponent.Check.isNullOrHidden(component)) return false;
185     final Point dragComponentPoint = SwingUtilities.convertPoint(me.getComponent(), me.getPoint(), myDragComponent);
186     return canStartDragging(myDragComponent, dragComponentPoint);
187   }
188
189   protected boolean canStartDragging(final JComponent dragComponent, Point dragComponentPoint) {
190     return true;
191   }
192
193
194   protected void processDragFinish(final MouseEvent event, boolean willDragOutStart) {
195   }
196
197   protected void processDragOutFinish(final MouseEvent event) {
198   }
199
200   public final boolean isDragJustStarted() {
201     return myDragJustStarted;
202   }
203
204   protected abstract void processDrag(MouseEvent event, Point dragToScreenPoint, Point startScreenPoint);
205
206   protected boolean isDragOut(MouseEvent event, Point dragToScreenPoint, Point startScreenPoint) {
207     return false;
208   }
209
210   protected void processDragOut(MouseEvent event, Point dragToScreenPoint, Point startScreenPoint, boolean justStarted) {
211
212   }
213
214   private boolean isWithinDeadZone(final MouseEvent e) {
215     final Point screen = new RelativePoint(e).getScreenPoint();
216     return Math.abs(myPressPointScreen.x - screen.x - myDelta.width) < DRAG_START_DEADZONE &&
217            Math.abs(myPressPointScreen.y - screen.y - myDelta.height) < DRAG_START_DEADZONE;
218   }
219
220   public void mouseClicked(final MouseEvent e) {
221   }
222
223   public void mouseEntered(final MouseEvent e) {
224   }
225
226   public void mouseExited(final MouseEvent e) {
227   }
228
229   public void mouseMoved(final MouseEvent e) {
230   }
231
232 }