View Javadoc
1   /**
2    * 
3    * Copyright (c) 2013-2014, Openflexo
4    * Copyright (c) 2011-2012, AgileBirds
5    * 
6    * This file is part of Gina-swing-editor, a component of the software infrastructure 
7    * developed at Openflexo.
8    * 
9    * 
10   * Openflexo is dual-licensed under the European Union Public License (EUPL, either 
11   * version 1.1 of the License, or any later version ), which is available at 
12   * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
13   * and the GNU General Public License (GPL, either version 3 of the License, or any 
14   * later version), which is available at http://www.gnu.org/licenses/gpl.html .
15   * 
16   * You can redistribute it and/or modify under the terms of either of these licenses
17   * 
18   * If you choose to redistribute it and/or modify under the terms of the GNU GPL, you
19   * must include the following additional permission.
20   *
21   *          Additional permission under GNU GPL version 3 section 7
22   *
23   *          If you modify this Program, or any covered work, by linking or 
24   *          combining it with software containing parts covered by the terms 
25   *          of EPL 1.0, the licensors of this Program grant you additional permission
26   *          to convey the resulting work. * 
27   * 
28   * This software is distributed in the hope that it will be useful, but WITHOUT ANY 
29   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
30   * PARTICULAR PURPOSE. 
31   *
32   * See http://www.openflexo.org/license.html for details.
33   * 
34   * 
35   * Please contact Openflexo (openflexo-contacts@openflexo.org)
36   * or visit www.openflexo.org if you need additional information.
37   * 
38   */
39  
40  package org.openflexo.gina.swing.editor.palette;
41  
42  import java.awt.Dimension;
43  import java.awt.Point;
44  import java.awt.datatransfer.DataFlavor;
45  import java.awt.datatransfer.UnsupportedFlavorException;
46  import java.awt.dnd.DnDConstants;
47  import java.awt.dnd.DropTargetDragEvent;
48  import java.awt.dnd.DropTargetDropEvent;
49  import java.awt.dnd.DropTargetEvent;
50  import java.awt.dnd.DropTargetListener;
51  import java.io.IOException;
52  import java.util.List;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  
56  import javax.swing.SwingUtilities;
57  
58  import org.openflexo.gina.model.FIBContainer;
59  import org.openflexo.gina.swing.editor.view.FIBSwingEditableContainerView;
60  import org.openflexo.gina.swing.editor.view.FIBSwingEditableContainerViewDelegate;
61  import org.openflexo.gina.swing.editor.view.FIBSwingEditableView;
62  import org.openflexo.gina.swing.editor.view.FIBSwingEditableViewDelegate.FIBDropTarget;
63  import org.openflexo.gina.swing.editor.view.PlaceHolder;
64  import org.openflexo.gina.swing.view.JFIBView;
65  import org.openflexo.gina.swing.view.container.JFIBPanelView;
66  import org.openflexo.logging.FlexoLogger;
67  import org.openflexo.swing.Focusable;
68  
69  /**
70   * DTListener a listener that tracks the state of the operation
71   * 
72   * @see java.awt.dnd.DropTargetListener
73   * @see java.awt.dnd.DropTarget
74   */
75  public class DropListener implements DropTargetListener {
76  
77  	static final Logger logger = FlexoLogger.getLogger(DropListener.class.getPackage().getName());
78  
79  	private final int acceptableActions = DnDConstants.ACTION_COPY | DnDConstants.ACTION_MOVE;
80  
81  	private final FIBSwingEditableView<?, ?> editableView;
82  
83  	private DropListener parentDropListener;
84  
85  	public DropListener(FIBSwingEditableView<?, ?> editableView) {
86  		this.editableView = editableView;
87  	}
88  
89  	public DropListener getParentDropListener() {
90  		return parentDropListener;
91  	}
92  
93  	public void setParentDropListener(DropListener parentDropListener) {
94  		this.parentDropListener = parentDropListener;
95  	}
96  
97  	public FIBSwingEditableView<?, ?> getEditableView() {
98  		return editableView;
99  	}
100 
101 	public Focusable getTargetComponent() {
102 		// return placeHolder != null ? placeHolder :
103 		// editableView.getDelegate();
104 		return editableView.getDelegate();
105 	}
106 
107 	/**
108 	 * Called by isDragOk Checks to see if the flavor drag flavor is acceptable
109 	 * 
110 	 * @param e
111 	 *            the DropTargetDragEvent object
112 	 * @return whether the flavor is acceptable
113 	 */
114 	private static boolean isDragFlavorSupported(DropTargetDragEvent e) {
115 		boolean ok = false;
116 		if (e.isDataFlavorSupported(ElementDrag.DEFAULT_FLAVOR)) {
117 			ok = true;
118 		}
119 		return ok;
120 	}
121 
122 	/**
123 	 * Called by drop Checks the flavors and operations
124 	 * 
125 	 * @param e
126 	 *            the DropTargetDropEvent object
127 	 * @return the chosen DataFlavor or null if none match
128 	 */
129 	private static DataFlavor chooseDropFlavor(DropTargetDropEvent e) {
130 		if (e.isLocalTransfer() == true && e.isDataFlavorSupported(ElementDrag.DEFAULT_FLAVOR)) {
131 			return ElementDrag.DEFAULT_FLAVOR;
132 		}
133 		return null;
134 	}
135 
136 	/**
137 	 * Called by dragEnter and dragOver Checks the flavors and operations
138 	 * 
139 	 * @param e
140 	 *            the event object
141 	 * @return whether the flavor and operation is ok
142 	 */
143 	private boolean isDragOk(DropTargetDragEvent e) {
144 		if (!isDragFlavorSupported(e)) {
145 			return false;
146 		}
147 
148 		int da = e.getDropAction();
149 		// we're saying that these actions are necessary
150 		if ((da & acceptableActions) == 0) {
151 			return false;
152 		}
153 
154 		try {
155 			FIBDraggable element = (FIBDraggable) e.getTransferable().getTransferData(ElementDrag.DEFAULT_FLAVOR);
156 			if (element == null) {
157 				return false;
158 			}
159 			Object source = e.getSource();
160 			if (source instanceof FIBDropTarget) {
161 				return element.acceptDragging((FIBDropTarget) source);
162 			}
163 			return false;
164 
165 		} catch (UnsupportedFlavorException e1) {
166 			logger.warning("Unexpected: " + e1);
167 			e1.printStackTrace();
168 			return false;
169 		} catch (IOException e1) {
170 			logger.warning("Unexpected: " + e1);
171 			e1.printStackTrace();
172 			return false;
173 		} catch (Exception e1) {
174 			logger.warning("Unexpected: " + e1);
175 			e1.printStackTrace();
176 			return false;
177 		}
178 	}
179 
180 	/**
181 	 * start "drag under" feedback on component invoke acceptDrag or rejectDrag based on isDragOk
182 	 * 
183 	 * @param e
184 	 */
185 	@Override
186 	public void dragEnter(DropTargetDragEvent e) {
187 		if (isDragOk(e)) {
188 
189 			Dimension preferredSize = new Dimension(25, 25); // Default size
190 
191 			// System.out.println("source=" + e.getSource());
192 			try {
193 				Object transferable = e.getTransferable().getTransferData(ElementDrag.DEFAULT_FLAVOR);
194 				if (transferable instanceof PaletteElement) {
195 					preferredSize = ((JFIBView<?, ?>) ((PaletteElement) transferable).getView()).getResultingJComponent().getSize();
196 				}
197 			} catch (UnsupportedFlavorException e1) {
198 				e1.printStackTrace();
199 			} catch (IOException e1) {
200 				e1.printStackTrace();
201 			}
202 
203 			// System.out.println("preferredSize=" + preferredSize);
204 
205 			if (editableView instanceof FIBSwingEditableContainerView) {
206 
207 				List<PlaceHolder> placeHolders = ((FIBSwingEditableContainerView<?, ?>) editableView).makePlaceHolders(preferredSize);
208 				getContainerDelegate().handlePlaceHolders(placeHolders);
209 
210 			}
211 
212 			// Otherwise, it's a widget, so dot it for the parent
213 			// I'm not totally confident with this code, especially in the conditions in which this should be applied
214 			// It is particulary usefull if you have a panel with border layout and a widget in center location, or a panel with box layout
215 			// Please remove this code if big issues are raised
216 			else if (editableView.getParentView() instanceof JFIBPanelView) {
217 				List<PlaceHolder> placeHolders = ((FIBSwingEditableContainerView<?, ?>) editableView.getParentView())
218 						.makePlaceHolders(preferredSize);
219 				getContainerDelegate().handlePlaceHolders(placeHolders);
220 
221 			}
222 
223 			fireDragEnter(e.getLocation());
224 
225 			// getTargetComponent().setFocused(true);
226 
227 			e.acceptDrag(e.getDropAction());
228 		}
229 		else {
230 			e.rejectDrag();
231 			return;
232 		}
233 	}
234 
235 	/**
236 	 * continue "drag under" feedback on component invoke acceptDrag or rejectDrag based on isDragOk
237 	 * 
238 	 * @param e
239 	 */
240 	@Override
241 	public void dragOver(DropTargetDragEvent e) {
242 		// System.out.println("dragOver... with " + e);
243 		fireDragOver(e.getLocation());
244 		if (isDragOk(e)) {
245 			e.acceptDrag(e.getDropAction());
246 		}
247 		else {
248 			e.rejectDrag();
249 		}
250 	}
251 
252 	@Override
253 	public void dropActionChanged(DropTargetDragEvent e) {
254 		if (isDragOk(e)) {
255 			e.acceptDrag(e.getDropAction());
256 		}
257 		else {
258 			e.rejectDrag();
259 		}
260 	}
261 
262 	private PlaceHolder showFocusedPlaceHolderIfAny(Point locationInView) {
263 		PlaceHolder focusedPH = null;
264 		if (getContainerDelegate().getPlaceholders() != null) {
265 			for (PlaceHolder ph : getContainerDelegate().getPlaceholders()) {
266 				if (ph.getBounds().contains(locationInView) && focusedPH == null) {
267 					// placeholder becomes visible
268 					// System.out.println("Un placeholder qui devient visible");
269 					ph.setVisible(true);
270 					focusedPH = ph;
271 				}
272 				else {
273 					ph.setVisible(false);
274 				}
275 			}
276 		}
277 		if (focusedPH != null) {
278 			// If a placeholder is focused, unfocus container
279 			getTargetComponent().setFocused(false);
280 		}
281 		else {
282 			// If no placeholder is focused, focus container
283 			getTargetComponent().setFocused(true);
284 		}
285 
286 		if (focusedPH == null && getContainerDelegate().getPlaceholders() != null && getContainerDelegate().getPlaceholders().size() == 1) {
287 			// special case where no placeholder is selected but only one
288 			// placeholder exists
289 			// in this case we want to show it
290 			getContainerDelegate().getPlaceholders().get(0).setVisible(true);
291 		}
292 
293 		if (editableView.getResultingJComponent() != null) {
294 			editableView.getResultingJComponent().repaint();
295 		}
296 
297 		return focusedPH;
298 	}
299 
300 	private PlaceHolder fireDragEnter(Point locationInView) {
301 
302 		if (getContainerDelegate() != null) {
303 			getContainerDelegate().getEditorController().dragEnter(this);
304 		}
305 
306 		if (getParentDropListener() != null && editableView.getParentView() != null) {
307 			PlaceHolder returned = getParentDropListener().fireDragEnter(SwingUtilities.convertPoint(editableView.getResultingJComponent(),
308 					locationInView, ((JFIBView<?, ?>) editableView.getParentView()).getResultingJComponent()));
309 			if (returned != null) {
310 				return returned;
311 			}
312 		}
313 
314 		return showFocusedPlaceHolderIfAny(locationInView);
315 
316 	}
317 
318 	private void fireDragExit() {
319 		if (getContainerDelegate() != null) {
320 			getContainerDelegate().dismissPlaceHolders();
321 			getContainerDelegate().deletePlaceHolders();
322 			getContainerDelegate().getEditorController().dragExit(this);
323 		}
324 
325 	}
326 
327 	private void fireDragEnd() {
328 
329 		if (getParentDropListener() != null) {
330 			getParentDropListener().fireDragEnd();
331 		}
332 
333 		if (getContainerDelegate() != null) {
334 			getContainerDelegate().deletePlaceHolders();
335 			getContainerDelegate().getEditorController().dragEnd(this);
336 		}
337 
338 	}
339 
340 	private PlaceHolder fireDragOver(Point locationInView) {
341 
342 		if (getParentDropListener() != null && editableView.getParentView() != null) {
343 			PlaceHolder returned = getParentDropListener().fireDragOver(SwingUtilities.convertPoint(editableView.getResultingJComponent(),
344 					locationInView, ((JFIBView<?, ?>) editableView.getParentView()).getResultingJComponent()));
345 			if (returned != null) {
346 				return returned;
347 			}
348 		}
349 
350 		return showFocusedPlaceHolderIfAny(locationInView);
351 
352 	}
353 
354 	@Override
355 	public void dragExit(DropTargetEvent e) {
356 		fireDragExit();
357 		getTargetComponent().setFocused(false);
358 	}
359 
360 	/**
361 	 * perform action from getSourceActions on the transferrable invoke acceptDrop or rejectDrop invoke dropComplete if its a local (same
362 	 * JVM) transfer, use StringTransferable.localStringFlavor find a match for the flavor check the operation get the transferable
363 	 * according to the chosen flavor do the transfer
364 	 * 
365 	 * @param e
366 	 */
367 	@Override
368 	public void drop(DropTargetDropEvent e) {
369 		// getContainerDelegate().removeFromPlaceHolderVisibleRequesters(getTargetComponent());
370 		getTargetComponent().setFocused(false);
371 		try {
372 			DataFlavor chosen = chooseDropFlavor(e);
373 			if (chosen == null) {
374 				e.rejectDrop();
375 				return;
376 			}
377 
378 			// the actions that the source has specified with
379 			// DragGestureRecognizer
380 			int sa = e.getSourceActions();
381 
382 			if ((sa & acceptableActions) == 0) {
383 				e.rejectDrop();
384 				return;
385 			}
386 
387 			Object data = null;
388 
389 			try {
390 
391 				/*
392 				 * the source listener receives this action in dragDropEnd. if
393 				 * the action is DnDConstants.ACTION_COPY_OR_MOVE then the
394 				 * source receives MOVE!
395 				 */
396 
397 				data = e.getTransferable().getTransferData(chosen);
398 				if (data == null) {
399 					throw new NullPointerException();
400 				}
401 				if (logger.isLoggable(Level.FINE)) {
402 					logger.fine("data is a " + data.getClass().getName());
403 				}
404 			} catch (Throwable t) {
405 				if (logger.isLoggable(Level.WARNING)) {
406 					logger.warning("Couldn't get transfer data: " + t.getMessage());
407 				}
408 				t.printStackTrace();
409 				e.dropComplete(false);
410 				return;
411 			}
412 
413 			if (data instanceof FIBDraggable) {
414 
415 				try {
416 					FIBDraggable element = (FIBDraggable) data;
417 					// FD remove code: element cannot be null due to the try catch
418 					// if (element == null) {
419 					// e.rejectDrop();
420 					// return;
421 					// }
422 					Object source = e.getSource();
423 
424 					// OK, let's got for the drop
425 					if (source instanceof FIBDropTarget && element.acceptDragging((FIBDropTarget) source)) {
426 						Point pt = e.getLocation();
427 						if (element.elementDragged((FIBDropTarget) source, this, pt)) {
428 							e.acceptDrop(acceptableActions);
429 							e.dropComplete(true);
430 							logger.info("Drop succeeded");
431 							return;
432 						}
433 					}
434 					e.rejectDrop();
435 					e.dropComplete(false);
436 					return;
437 
438 				} catch (Exception e1) {
439 					logger.warning("Unexpected: " + e1);
440 					e1.printStackTrace();
441 					e.rejectDrop();
442 					e.dropComplete(false);
443 					return;
444 				}
445 
446 			}
447 
448 			e.rejectDrop();
449 			e.dropComplete(false);
450 			return;
451 		}
452 
453 		finally {
454 			fireDragEnd();
455 		}
456 	}
457 
458 	public FIBSwingEditableContainerViewDelegate<?, ?> getContainerDelegate() {
459 		if (!(editableView.getComponent() instanceof FIBContainer)) {
460 			if (editableView.getParentView() != null && editableView.getParentView() instanceof FIBSwingEditableView) {
461 				return ((FIBSwingEditableContainerView<?, ?>) editableView.getParentView()).getDelegate();
462 			}
463 		}
464 		if (editableView instanceof FIBSwingEditableContainerView) {
465 			return ((FIBSwingEditableContainerView<?, ?>) editableView).getDelegate();
466 		}
467 		// System.out.println("editableView=" + editableView);
468 		// System.out.println("editableView.getParentView()=" + editableView.getParentView());
469 		return null;
470 	}
471 
472 }