001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016: package org.columba.mail.gui.tree;
017:
018: import java.awt.Point;
019: import java.awt.datatransfer.DataFlavor;
020: import java.awt.datatransfer.Transferable;
021: import java.awt.dnd.DropTarget;
022: import java.awt.dnd.DropTargetDragEvent;
023: import java.awt.dnd.DropTargetDropEvent;
024: import java.awt.dnd.DropTargetEvent;
025: import java.awt.event.ActionEvent;
026: import java.awt.event.ActionListener;
027:
028: import javax.swing.JComponent;
029: import javax.swing.JTree;
030: import javax.swing.Timer;
031: import javax.swing.ToolTipManager;
032: import javax.swing.TransferHandler;
033: import javax.swing.tree.DefaultMutableTreeNode;
034: import javax.swing.tree.TreePath;
035:
036: import org.columba.core.xml.XmlElement;
037: import org.columba.mail.config.IFolderItem;
038: import org.columba.mail.folder.IMailFolder;
039: import org.frapuccino.swing.SortedJTree;
040:
041: /**
042: * this class does all the dirty work for the TreeController
043: */
044: public class TreeView extends SortedJTree {
045: /** The treepath that was selected before the drag and drop began. */
046: private TreePath selectedPathBeforeDrag;
047:
048: /** The treepath that is under the mouse pointer in a drag and drop action. */
049: private TreePath dropTargetPath;
050:
051: /** The component is in a drag and drop action */
052: private boolean isInDndMode = false;
053:
054: /**
055: * A Timer that expands/collapses leafs when the mouse hovers above it. This
056: * is only used during Drag and Drop.
057: */
058: private Timer dndAutoExpanderTimer;
059:
060: /**
061: * Constructa a tree view
062: *
063: * @param model
064: * the tree model that this JTree should use.
065: */
066: public TreeView(javax.swing.tree.TreeModel model) {
067: super (model);
068:
069: ToolTipManager.sharedInstance().registerComponent(this );
070:
071: putClientProperty("JTree.lineStyle", "Angled");
072:
073: setShowsRootHandles(true);
074: setRootVisible(false);
075:
076: //setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
077: IMailFolder root = (IMailFolder) treeModel.getRoot();
078:
079: expand(root);
080:
081: repaint();
082:
083: setDropTarget(new DropHandler());
084:
085: dndAutoExpanderTimer = new Timer(1000,
086: new TreeLeafActionListener(this ));
087: dndAutoExpanderTimer.setRepeats(false);
088: }
089:
090: /**
091: * Expands the specified node so it corresponds to the expanded attribute in
092: * the configuration.
093: *
094: * @param parent
095: * node to check if it should be expanded or not.
096: */
097: public final void expand(IMailFolder parent) {
098: // get configuration from tree.xml file
099: IFolderItem item = parent.getConfiguration();
100:
101: XmlElement property = item.getElement("property");
102:
103: if (property != null) {
104: String expanded = property.getAttribute("expanded");
105:
106: if (expanded == null) {
107: expanded = "true";
108: }
109:
110: // expand folder
111: int row = getRowForPath(new TreePath(parent.getPath()));
112:
113: if (expanded.equals("true")) {
114: expandRow(row);
115: }
116: }
117:
118: // recursivly expand all children
119: for (int i = 0; i < parent.getChildCount(); i++) {
120: IMailFolder child = (IMailFolder) parent.getChildAt(i);
121: expand(child);
122: }
123: }
124:
125: /**
126: * Returns the tree node that is intended for a drop action. If this method
127: * is called during a non-drag-and-drop invocation there is no guarantee
128: * what it will return.
129: *
130: * @return the folder tree node that is targeted for the drop action; null
131: * otherwise.
132: */
133: public IMailFolder getDropTargetFolder() {
134: IMailFolder node = null;
135:
136: if (dropTargetPath != null) {
137: node = (IMailFolder) dropTargetPath.getLastPathComponent();
138: } else {
139: if (getSelectionPath() != null)
140: node = (IMailFolder) getSelectionPath()
141: .getLastPathComponent();
142: }
143:
144: return node;
145: }
146:
147: /**
148: * Sets the stored drop target path to null. This should be done after the
149: * getDropTargetFolder() has been used in a folder command.
150: */
151: void resetDropTargetFolder() {
152: dropTargetPath = null;
153: }
154:
155: /**
156: * Returns the tree node that was selected before a drag and drop was
157: * initiated. If this method is called during a non-drag-and-drop invocation
158: * there is no guarantee what it will return.
159: *
160: * @return the folder that is being dragged; null if it wasnt initiated in
161: * this component.
162: */
163: public DefaultMutableTreeNode getSelectedNodeBeforeDragAction() {
164: DefaultMutableTreeNode node = null;
165:
166: if (selectedPathBeforeDrag != null) {
167: node = (DefaultMutableTreeNode) selectedPathBeforeDrag
168: .getLastPathComponent();
169: }
170:
171: return node;
172: }
173:
174: /**
175: * Returns true if the tree is in a Drag and Drop action.
176: *
177: * @return true if the tree is in a Drag and Drop action; false otherwise.
178: */
179: public boolean isInDndAction() {
180: return isInDndMode;
181: }
182:
183: /**
184: * Sets up this TreeView for Drag and drop action. Stores the selected tree
185: * leaf before the action begins, this is used later when the Drag and drop
186: * action is completed.
187: */
188: private void setUpDndAction() {
189: isInDndMode = true;
190: selectedPathBeforeDrag = getSelectionPath();
191: }
192:
193: /**
194: * Resets this TreeView after a Drag and drop action has occurred. Selects
195: * the previous selected tree leaf before the DnD action began.
196: */
197: private void resetDndAction() {
198: dndAutoExpanderTimer.stop();
199: setSelectionPath(selectedPathBeforeDrag);
200: selectedPathBeforeDrag = null;
201: isInDndMode = false;
202: }
203:
204: /**
205: * Our own drop target implementation. This treeview class uses its own drop
206: * target since the common drop target in Swing >1.4 does not provide a fine
207: * grained support for dragging items onto leafs, when some leafs does not
208: * accept new items.
209: *
210: * @author redsolo
211: */
212:
213: private class DropHandler extends DropTarget {
214: private boolean canImport;
215:
216: /** The latest mouse location. */
217: private Point location;
218:
219: /**
220: * Our own implementation to ask the transfer handler for each leaf the
221: * user moves above. {@inheritDoc}
222: */
223: public void dragOver(DropTargetDragEvent e) {
224: if ((location == null)
225: || (!location.equals(e.getLocation()))) {
226: location = e.getLocation();
227:
228: TreePath targetPath = getClosestPathForLocation(
229: location.x, location.y);
230:
231: if ((dropTargetPath != null)
232: && (targetPath == dropTargetPath)) {
233: return;
234: }
235:
236: dropTargetPath = targetPath;
237:
238: dndAutoExpanderTimer.restart();
239:
240: TreeView.this .getSelectionModel().setSelectionPath(
241: dropTargetPath);
242:
243: DataFlavor[] flavors = e.getCurrentDataFlavors();
244:
245: JComponent c = (JComponent) e.getDropTargetContext()
246: .getComponent();
247: TransferHandler importer = c.getTransferHandler();
248:
249: if ((importer != null)
250: && importer.canImport(c, flavors)) {
251: canImport = true;
252: } else {
253: canImport = false;
254: }
255:
256: int dropAction = e.getDropAction();
257:
258: if (canImport) {
259: e.acceptDrag(dropAction);
260: } else {
261: e.rejectDrag();
262: }
263: }
264: }
265:
266: /** {@inheritDoc} */
267: public void dragEnter(DropTargetDragEvent e) {
268: setUpDndAction();
269:
270: DataFlavor[] flavors = e.getCurrentDataFlavors();
271:
272: JComponent c = (JComponent) e.getDropTargetContext()
273: .getComponent();
274: TransferHandler importer = c.getTransferHandler();
275:
276: if ((importer != null) && importer.canImport(c, flavors)) {
277: canImport = true;
278: } else {
279: canImport = false;
280: }
281:
282: int dropAction = e.getDropAction();
283:
284: if (canImport) {
285: e.acceptDrag(dropAction);
286: } else {
287: e.rejectDrag();
288: }
289: }
290:
291: /** {@inheritDoc} */
292: public void dragExit(DropTargetEvent e) {
293: resetDndAction();
294: dropTargetPath = null;
295: }
296:
297: /** {@inheritDoc} */
298: public void drop(DropTargetDropEvent e) {
299: int dropAction = e.getDropAction();
300:
301: JComponent c = (JComponent) e.getDropTargetContext()
302: .getComponent();
303: TransferHandler importer = c.getTransferHandler();
304:
305: if (canImport && (importer != null)) {
306: e.acceptDrop(dropAction);
307:
308: try {
309: Transferable t = e.getTransferable();
310: e.dropComplete(importer.importData(c, t));
311: } catch (RuntimeException re) {
312: e.dropComplete(false);
313: }
314: } else {
315: e.rejectDrop();
316: }
317:
318: resetDndAction();
319: }
320:
321: /** {@inheritDoc} */
322: public void dropActionChanged(DropTargetDragEvent e) {
323: int dropAction = e.getDropAction();
324:
325: if (canImport) {
326: e.acceptDrag(dropAction);
327: } else {
328: e.rejectDrag();
329: }
330: }
331: }
332:
333: /**
334: * An ActionListener that collapses/expands leafs in a tree.
335: *
336: * @author redsolo
337: */
338: private class TreeLeafActionListener implements ActionListener {
339: /**
340: * Constructs a leaf listener.
341: *
342: * @param parent
343: * the parent JTree.
344: */
345: public TreeLeafActionListener(JTree parent) {
346: }
347:
348: /** {@inheritDoc} */
349: public void actionPerformed(ActionEvent e) {
350: // Do nothing if we are hovering over the root node
351: if (dropTargetPath != null) {
352: if (isExpanded(dropTargetPath)) {
353: collapsePath(dropTargetPath);
354: } else {
355: expandPath(dropTargetPath);
356: }
357: }
358: }
359: }
360: }
|