1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.custom;
15 
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.dnd.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.widgets.*;
20 
21 /**
22  * This adapter class provides a default drag under effect (eg. select and scroll)
23  * when a drag occurs over a <code>StyledText</code>.
24  *
25  * <p>Classes that wish to provide their own drag under effect for a <code>StyledText</code>
26  * can extend this class, override the <code>StyledTextDropTargetEffect.dragOver</code>
27  * method and override any other applicable methods in <code>StyledTextDropTargetEffect</code> to
28  * display their own drag under effect.</p>
29  *
30  * Subclasses that override any methods of this class should call the corresponding
31  * <code>super</code> method to get the default drag under effect implementation.
32  *
33  * <p>The feedback value is either one of the FEEDBACK constants defined in
34  * class <code>DND</code> which is applicable to instances of this class,
35  * or it must be built by <em>bitwise OR</em>'ing together
36  * (that is, using the <code>int</code> "|" operator) two or more
37  * of those <code>DND</code> effect constants.
38  * </p>
39  * <dl>
40  * <dt><b>Feedback:</b></dt>
41  * <dd>FEEDBACK_SELECT, FEEDBACK_SCROLL</dd>
42  * </dl>
43  *
44  * @see DropTargetAdapter
45  * @see DropTargetEvent
46  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
47  *
48  * @since 3.3
49  */
50 public class StyledTextDropTargetEffect extends DropTargetEffect {
51 	static final int CARET_WIDTH = 2;
52 	static final int SCROLL_HYSTERESIS = 100; // milli seconds
53 	static final int SCROLL_TOLERANCE = 20; // pixels
54 
55 	int currentOffset = -1;
56 	long scrollBeginTime;
57 	int scrollX = -1, scrollY = -1;
58 	Listener paintListener;
59 
60 	/**
61 	 * Creates a new <code>StyledTextDropTargetEffect</code> to handle the drag under effect on the specified
62 	 * <code>StyledText</code>.
63 	 *
64 	 * @param styledText the <code>StyledText</code> over which the user positions the cursor to drop the data
65 	 */
StyledTextDropTargetEffect(StyledText styledText)66 	public StyledTextDropTargetEffect(StyledText styledText) {
67 		super(styledText);
68 		paintListener = event -> {
69 			if (currentOffset != -1) {
70 				StyledText text = (StyledText) getControl();
71 				Point position = text.getLocationAtOffset(currentOffset);
72 				int height = text.getLineHeight(currentOffset);
73 				event.gc.setBackground(event.display.getSystemColor (SWT.COLOR_BLACK));
74 				event.gc.fillRectangle(position.x, position.y, CARET_WIDTH, height);
75 			}
76 		};
77 	}
78 
79 	/**
80 	 * This implementation of <code>dragEnter</code> provides a default drag under effect
81 	 * for the feedback specified in <code>event.feedback</code>.
82 	 *
83 	 * For additional information see <code>DropTargetAdapter.dragEnter</code>.
84 	 *
85 	 * Subclasses that override this method should call <code>super.dragEnter(event)</code>
86 	 * to get the default drag under effect implementation.
87 	 *
88 	 * @param event  the information associated with the drag start event
89 	 *
90 	 * @see DropTargetAdapter
91 	 * @see DropTargetEvent
92 	 */
93 	@Override
dragEnter(DropTargetEvent event)94 	public void dragEnter(DropTargetEvent event) {
95 		currentOffset = -1;
96 		scrollBeginTime = 0;
97 		scrollX = -1;
98 		scrollY = -1;
99 		getControl().removeListener(SWT.Paint, paintListener);
100 		getControl().addListener (SWT.Paint, paintListener);
101 	}
102 
103 	/**
104 	 * This implementation of <code>dragLeave</code> provides a default drag under effect
105 	 * for the feedback specified in <code>event.feedback</code>.
106 	 *
107 	 * For additional information see <code>DropTargetAdapter.dragLeave</code>.
108 	 *
109 	 * Subclasses that override this method should call <code>super.dragLeave(event)</code>
110 	 * to get the default drag under effect implementation.
111 	 *
112 	 * @param event  the information associated with the drag leave event
113 	 *
114 	 * @see DropTargetAdapter
115 	 * @see DropTargetEvent
116 	 */
117 	@Override
dragLeave(DropTargetEvent event)118 	public void dragLeave(DropTargetEvent event) {
119 		StyledText text = (StyledText) getControl();
120 		if (currentOffset != -1) {
121 			refreshCaret(text, currentOffset, -1);
122 		}
123 		text.removeListener(SWT.Paint, paintListener);
124 		scrollBeginTime = 0;
125 		scrollX = -1;
126 		scrollY = -1;
127 	}
128 
129 	/**
130 	 * This implementation of <code>dragOver</code> provides a default drag under effect
131 	 * for the feedback specified in <code>event.feedback</code>.
132 	 *
133 	 * For additional information see <code>DropTargetAdapter.dragOver</code>.
134 	 *
135 	 * Subclasses that override this method should call <code>super.dragOver(event)</code>
136 	 * to get the default drag under effect implementation.
137 	 *
138 	 * @param event  the information associated with the drag over event
139 	 *
140 	 * @see DropTargetAdapter
141 	 * @see DropTargetEvent
142 	 * @see DND#FEEDBACK_SELECT
143 	 * @see DND#FEEDBACK_SCROLL
144 	 */
145 	@Override
dragOver(DropTargetEvent event)146 	public void dragOver(DropTargetEvent event) {
147 		int effect = event.feedback;
148 		StyledText text = (StyledText) getControl();
149 
150 		Point pt = text.getDisplay().map(null, text, event.x, event.y);
151 		if ((effect & DND.FEEDBACK_SCROLL) == 0) {
152 			scrollBeginTime = 0;
153 			scrollX = scrollY = -1;
154 		} else {
155 			if (text.getCharCount() == 0) {
156 				scrollBeginTime = 0;
157 				scrollX = scrollY = -1;
158 			} else {
159 				if (scrollX != -1 && scrollY != -1 && scrollBeginTime != 0 &&
160 					(pt.x >= scrollX && pt.x <= (scrollX + SCROLL_TOLERANCE) ||
161 					 pt.y >= scrollY && pt.y <= (scrollY + SCROLL_TOLERANCE))) {
162 					if (System.currentTimeMillis() >= scrollBeginTime) {
163 						Rectangle area = text.getClientArea();
164 						GC gc = new GC(text);
165 						FontMetrics fm = gc.getFontMetrics();
166 						gc.dispose();
167 						double charWidth = fm.getAverageCharacterWidth();
168 						int scrollAmount = (int) (10*charWidth);
169 						if (pt.x < area.x + 3*charWidth) {
170 							int leftPixel = text.getHorizontalPixel();
171 							text.setHorizontalPixel(leftPixel - scrollAmount);
172 						}
173 						if (pt.x > area.width - 3*charWidth) {
174 							int leftPixel = text.getHorizontalPixel();
175 							text.setHorizontalPixel(leftPixel + scrollAmount);
176 						}
177 						int lineHeight = text.getLineHeight();
178 						if (pt.y < area.y + lineHeight) {
179 							int topPixel = text.getTopPixel();
180 							text.setTopPixel(topPixel - lineHeight);
181 						}
182 						if (pt.y > area.height - lineHeight) {
183 							int topPixel = text.getTopPixel();
184 							text.setTopPixel(topPixel + lineHeight);
185 						}
186 						scrollBeginTime = 0;
187 						scrollX = scrollY = -1;
188 					}
189 				} else {
190 					scrollBeginTime = System.currentTimeMillis() + SCROLL_HYSTERESIS;
191 					scrollX = pt.x;
192 					scrollY = pt.y;
193 				}
194 			}
195 		}
196 
197 		if ((effect & DND.FEEDBACK_SELECT) != 0) {
198 			int[] trailing = new int [1];
199 			int newOffset = text.getOffsetAtPoint(pt.x, pt.y, trailing, false);
200 			newOffset += trailing [0];
201 			if (newOffset != currentOffset) {
202 				refreshCaret(text, currentOffset, newOffset);
203 				currentOffset = newOffset;
204 			}
205 		}
206 	}
207 
refreshCaret(StyledText text, int oldOffset, int newOffset)208 	void refreshCaret(StyledText text, int oldOffset, int newOffset) {
209 		if (oldOffset != newOffset) {
210 			if (oldOffset != -1) {
211 				Point oldPos = text.getLocationAtOffset(oldOffset);
212 				int oldHeight = text.getLineHeight(oldOffset);
213 				text.redraw (oldPos.x, oldPos.y, CARET_WIDTH, oldHeight, false);
214 			}
215 			if (newOffset != -1) {
216 				Point newPos = text.getLocationAtOffset(newOffset);
217 				int newHeight = text.getLineHeight(newOffset);
218 				text.redraw (newPos.x, newPos.y, CARET_WIDTH, newHeight, false);
219 			}
220 		}
221 	}
222 
223 	/**
224 	 * This implementation of <code>dropAccept</code> provides a default drag under effect
225 	 * for the feedback specified in <code>event.feedback</code>.
226 	 *
227 	 * For additional information see <code>DropTargetAdapter.dropAccept</code>.
228 	 *
229 	 * Subclasses that override this method should call <code>super.dropAccept(event)</code>
230 	 * to get the default drag under effect implementation.
231 	 *
232 	 * @param event  the information associated with the drop accept event
233 	 *
234 	 * @see DropTargetAdapter
235 	 * @see DropTargetEvent
236 	 */
237 	@Override
dropAccept(DropTargetEvent event)238 	public void dropAccept(DropTargetEvent event) {
239 		if (currentOffset != -1) {
240 			StyledText text = (StyledText) getControl();
241 			text.setSelection(currentOffset);
242 			currentOffset = -1;
243 		}
244 	}
245 }
246