Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
CueTree.java
1 
16 package net.squiz.cuetree;
17 
18 import javax.swing.*;
19 import javax.swing.tree.*;
20 import java.awt.*;
21 import java.awt.image.*;
22 import java.awt.event.*;
23 import javax.swing.plaf.*;
24 import javax.swing.event.*;
25 import javax.swing.plaf.basic.BasicTreeUI;
26 
27 import net.squiz.matrix.ui.*;
28 import net.squiz.matrix.core.*;
29 import net.squiz.matrix.matrixtree.*;
30 import net.squiz.matrix.plaf.*;
31 
38 public class CueTree extends JTree {
39 
41  public static final int DRAG_MODE = 1;
43  public static final int CLICK_MODE = 2;
47  public static final int ADD_REQUEST_MODE = 1;
49  public static final int MOVE_REQUEST_MODE = 2;
51  public static final String INVALID_CURSOR = "Invalid.32x32";
53  public static final Rectangle VOID_RECTANGLE = new Rectangle(0, 0, 0, 0);
54 
56  protected boolean inCueMode = false;
58  protected int cueLineOffset = 5;
59  /* the dirty bounds that the cue was drawn */
60  protected Rectangle dirtyCueBounds = VOID_RECTANGLE;
61  /* the dirty bounds that the cue was drawn */
62  protected Rectangle dirtyGhostBounds = VOID_RECTANGLE;
63 
64  private Image ghostedNode = null;
65  private TreePath currentPath = null;
66  private TreePath[] sourcePaths = null;
67  private boolean showsGhostedNode = false;
68  /* stroke used for highliting paths */
69  private Stroke highlightStroke;
70  /* stroke used for the cue line */
71  private Stroke cueLineStroke;
72  private Color cueLineColor = Color.BLACK;
73 
74  /*
75  * if TRUE, then the last path that the cue line was drawn for will
76  * be the new parent for the moving/adding node. If FALSE, then the node
77  * will be added/moved the the exact location as the cue and on the same
78  * branch as the currentPath
79  */
80  private boolean lastPathWasParent = false;
81 
82  /*
83  * TRUE if the cue line is abover the top most path in the tree. used
84  * so we can move items to the top of the tree
85  */
86  private boolean aboveTopPath = false;
87 
88  private boolean moveEnabled = true;
89  private int requestMode = MOVE_REQUEST_MODE;
90  private CueGestureHandler cueGestureHandler = null;
91  private Cursor moveCursor = new Cursor(Cursor.HAND_CURSOR);
92  private Cursor noMoveCursor;
93  private boolean triggersPath = true;
94 
98  public CueTree() {
99  super(getDefaultTreeModel());
100  init();
101  }
102 
107  public CueTree(TreeModel model) {
108  super(model);
109  init();
110  }
111 
115  private void init() {
116  cueGestureHandler = getCueGestureHandler();
117  setInvalidCursor();
118  setUI(new CueTreeUI());
119  setMoveEnabled(true);
120  addKeyListener(new KeyListener());
121  }
122 
127  private void setInvalidCursor() {
128  try {
129  noMoveCursor = Cursor.getSystemCustomCursor(INVALID_CURSOR);
130  } catch (Exception e) {
131  e.printStackTrace();
132  noMoveCursor = Cursor.getDefaultCursor();
133  }
134  }
135 
142  listenerList.add(CueGestureListener.class, cgl);
143  }
144 
151  listenerList.remove(CueGestureListener.class, cgl);
152  }
153 
159  return new CueGestureHandler();
160  }
161 
172  public void setShowsGhostedNode(boolean b) {
173  showsGhostedNode = b;
174  }
175 
181  public boolean getShowsGhostedNode() {
182  return showsGhostedNode;
183  }
184 
190  public void setMoveEnabled(boolean b) {
191  moveEnabled = b;
192  if (b == false) {
193  removeMouseListener(cueGestureHandler);
194  removeMouseMotionListener(cueGestureHandler);
195  } else {
196  addMouseListener(cueGestureHandler);
197  addMouseMotionListener(cueGestureHandler);
198  }
199  }
200 
205  public boolean isMoveEnabled() {
206  return moveEnabled;
207  }
208 
209  // TODO: (MM) remove this if its obsolete
214  public TreePath getCuePath() {
215  return inCueMode ? sourcePaths[0] : null;
216  }
217 
225  public void setCueLineStroke(Stroke stroke) {
226  cueLineStroke = stroke;
227  }
228 
236  public Stroke getCueLineStroke() {
237  return cueLineStroke;
238  }
239 
245  public void setCueLineColor(Color color) {
246  cueLineColor = color;
247  }
248 
254  public Color getCueLineColor() {
255  return cueLineColor;
256  }
257 
266  public void setHighlightPathStroke(Stroke stroke) {
267  highlightStroke = stroke;
268  }
269 
278  public Stroke getHighlightPathStroke() {
279  return highlightStroke;
280  }
281 
287  public void setMoveCursor(Cursor cursor) {
288  moveCursor = cursor;
289  }
290 
296  public void setNoMoveCursor(Cursor cursor) {
297  noMoveCursor = cursor;
298  }
299 
307  public void setCueLineOffset(int offset) {
308  cueLineOffset = offset;
309  }
310 
311  public boolean inCueMode() {
312  return inCueMode;
313  }
314 
320  protected Component getComponentForPath(TreePath path) {
321  if (path == null)
322  throw new IllegalArgumentException("path is null");
323  TreeCellRenderer renderer = getCellRenderer();
324  Object node = path.getLastPathComponent();
325 
326  TreeUI ui = getUI();
327  if (ui != null) {
328  int row = ui.getRowForPath(this, path);
329  int lsr = getLeadSelectionRow();
330  boolean hasFocus = isFocusOwner()
331  && (lsr == row);
332 
333  return renderer.getTreeCellRendererComponent(
334  this,
335  node,
336  isPathSelected(path),
337  isExpanded(path),
338  getModel().isLeaf(node),
339  row,
340  hasFocus
341  );
342  }
343  return null;
344  }
345 
353  public Rectangle getPathBounds(TreePath[] paths) {
354  if (paths.length == 1)
355  return getPathBounds(paths[0]);
356 
357  Rectangle bounds = new Rectangle();
358  // create a rectangle that is the union of all the specified paths
359  for (int i = 0; i < paths.length; i++) {
360  if (paths[i] != null)
361  bounds.add(getPathBounds(paths[i]));
362  }
363  return bounds;
364  }
365 
372  public Image getGhostedNode(TreePath path) {
373  Component c = getComponentForPath(path);
374  if (c == null)
375  throw new NullPointerException("Cannot create Ghosted Node: " +
376  " component is null");
377 
378  Rectangle bounds = getPathBounds(path);
379  // need to set the size of the component
380  c.setSize(bounds.width, bounds.height);
381 
382  BufferedImage image = new BufferedImage(
383  bounds.width,
384  bounds.height,
385  BufferedImage.TYPE_INT_ARGB_PRE
386  );
387 
388  Graphics2D g2d = (Graphics2D) image.createGraphics();
389  g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.3f));
390  c.paint(g2d);
391  g2d.dispose();
392 
393  return image;
394  }
395 
402  public Image getGhostedNode(TreePath[] paths) {
403  Rectangle bounds = getPathBounds(paths);
404  BufferedImage image = new BufferedImage(
405  bounds.width,
406  bounds.height,
407  BufferedImage.TYPE_INT_ARGB_PRE
408  );
409  Graphics2D g2d = (Graphics2D) image.createGraphics();
410 
411  // paint all the paths to the image in the location
412  // that they exist in the tree
413  for (int i = 0; i < paths.length; i++) {
414  Image pathImage = getGhostedNode(paths[i]);
415  Rectangle pathBounds = getPathBounds(paths[i]);
416  g2d.drawImage(pathImage, pathBounds.x, pathBounds.y, null);
417  }
418  g2d.dispose();
419 
420  return image;
421  }
422 
429  protected void paintGhostedNode(int x, int y, TreePath path) {
430  if (ghostedNode == null)
431  ghostedNode = getGhostedNode(path);
432  Graphics g = getGraphics();
433  g.drawImage(ghostedNode, x, y, this);
434  Rectangle bounds = getPathBounds(path);
435  dirtyGhostBounds.setRect(x, y, bounds.width, bounds.height);
436  }
437 
450  public Rectangle getNodeIconBounds(TreePath path) {
451  if (path == null)
452  throw new IllegalArgumentException("path is null");
453 
454  Component c = getComponentForPath(path);
455 
456  if (c == null)
457  return null;
458  if (!(c instanceof JLabel))
459  return null;
460 
461  // get the current bounds of the path so we can use
462  // its x and y co-ordinates
463  Rectangle bounds = getPathBounds(path);
464  Icon icon = ((JLabel) c).getIcon();
465  if (icon == null)
466  return null;
467 
468  bounds.setSize(icon.getIconWidth(), icon.getIconHeight());
469 
470  return bounds;
471  }
472 
483  protected boolean nodeIconContainsPoint(TreePath path, Point point) {
484  if (path == null)
485  return false;
486  Rectangle bounds = getNodeIconBounds(path);
487  if (bounds == null)
488  return false;
489 
490  return (bounds.contains(point));
491  }
492 
500  protected boolean nodeIconContainsPoint(Point point) {
501  TreePath path = getPathForLocation(point.x, point.y);
502  if (path == null)
503  return false;
504 
505  return nodeIconContainsPoint(path, point);
506  }
507 
521  public void initiateAddMode(TreePath[] paths, Point initPoint) {
522  if (inCueMode)
523  return;
524  requestMode = ADD_REQUEST_MODE;
525  fireAddGestureRecognized(paths, null, -1, initPoint);
526  startCueMode(paths);
527  // initially draw a line where the add gesture originated
528  drawCueLine(getClosestPathForLocation(initPoint.x, initPoint.y), initPoint.y, true);
529  }
530 
537  public void startCueMode(TreePath[] paths) {
538  if (inCueMode)
539  return;
540  // set the cursor back to the default cursor,
541  // as the cursor will still be a hand from clicking the icon
542  setCursor(Cursor.getDefaultCursor());
543  sourcePaths = paths;
544  inCueMode = true;
545  }
546 
551  public void stopCueMode() {
552  paintImmediately(dirtyCueBounds);
553  inCueMode = false;
554  ghostedNode = null;
555  requestMode = MOVE_REQUEST_MODE;
556  }
557 
564  public void setTriggersPath(boolean b) {
565  triggersPath = b;
566  }
567 
577  protected void drawCueLine(TreePath path, boolean pathIsNewParent, boolean aboveTopPath) {
578 
579  paintImmediately(dirtyCueBounds);
580  Rectangle bounds = getPathBounds(path);
581  Graphics2D g2d = (Graphics2D) getGraphics();
582 
583  int x1 = 0, x2 = 0, y = 0, ExpandingNodeModifier = 0;
584 
585  boolean updateNode = false;
586  TreeNode node = (TreeNode)path.getLastPathComponent();
587  if (pathIsNewParent) {
588 
589  if (node instanceof ExpandingNextNode) {
590  if (!((ExpandingNode) node).usingCueModeName()) {
591  ((ExpandingNode) node).useCueModeName();
592  updateNode = true;
593  }
594  ExpandingNodeModifier = ((ExpandingNode) node).getInitStrWidth();
595  } else if (node instanceof ExpandingPreviousNode) {
596  if (!((ExpandingNode) node).usingCueModeName()) {
597  ((ExpandingNode) node).useCueModeName();
598  updateNode = true;
599  }
600  ExpandingNodeModifier = ((ExpandingNode) node).getInitStrWidth();
601  }
602 
603  if (updateNode) {
604  ((DefaultTreeModel) getModel()).nodeChanged(node);
605  }
606 
607  dirtyCueBounds = highlightPath(path, UIManager.getColor("CueLine.stroke"));
608  if (ExpandingNodeModifier > 0) {
609  bounds.width = ExpandingNodeModifier;
610  }
611  x1 = bounds.x + bounds.width;
612  y = bounds.y + (bounds.height / 2);
613 
614  } else {
615 
616  if ((node instanceof ExpandingNextNode) || ((MatrixTreeNode)node.getParent()).hasNextNode()) {
617  if (!(node instanceof ExpandingNextNode)) {
618  node = node.getParent().getChildAt(node.getParent().getChildCount()-1);
619  }
620  if (((ExpandingNode)node).usingCueModeName()) {
621  ((ExpandingNode) node).setName(((ExpandingNode) node).getName());
622  ((DefaultTreeModel) getModel()).nodeChanged(node);
623  }
624  }
625 
626  if (((MatrixTreeNode)node.getParent()).hasPreviousNode()) {
627  node = ((MatrixTreeNode)node.getParent()).getChildAt(0);
628  } else if (((MatrixTreeNode)node).hasPreviousNode()) {
629  node = ((MatrixTreeNode)node).getChildAt(0);
630  }
631 
632  if ((node instanceof ExpandingPreviousNode)) {
633 
634  if (((ExpandingNode)node).usingCueModeName()) {
635  ((ExpandingNode) node).setName(((ExpandingNode) node).getName());
636  ((DefaultTreeModel) getModel()).nodeChanged(node);
637  }
638  }
639 
640  if (isExpanded(path) && getModel().isLeaf(path.getLastPathComponent())) {
641  // if the path given is not the new parent then we
642  // want to draw a cue line so that it's between the first
643  // node on this branch and the parent node
644 
645  // get the first child on the branch so we can use its x co-ords
646  Object child = getModel().getChild(path.getLastPathComponent(), 0);
647  Rectangle childBounds = getPathBounds(path.pathByAddingChild(child));
648  x1 = childBounds.x;
649  } else {
650  dirtyCueBounds = VOID_RECTANGLE;
651  x1 = bounds.x;
652  }
653  if (!aboveTopPath)
654  y = bounds.y + bounds.height;
655  }
656 
657  x2 = getX() + getWidth();
658 
659  int lineWidth = 1;
660  if (cueLineStroke != null) {
661  g2d.setStroke(cueLineStroke);
662  if (highlightStroke instanceof BasicStroke)
663  lineWidth = (int) ((BasicStroke) highlightStroke).getLineWidth();
664  }
665 
666  // create a union of the current bounds of the cue
667  // and the line that we are about to draw
668  dirtyCueBounds.add(new Rectangle(x1, y, Math.abs(x2 - x1), lineWidth));
669  g2d.setColor(UIManager.getColor("CueLine.stroke"));
670  g2d.drawLine(x1, y, x2, y);
671 
672 
673  }
674 
683  protected void drawCueLine(TreePath path, int mouseY) {
684  drawCueLine(path, mouseY, false);
685  }
686 
687  private void drawCueLine(TreePath path, int mouseY, boolean forceRedraw) {
688  boolean prevPathWasParent = lastPathWasParent;
689 
690  if (mouseY < 3) {
691  lastPathWasParent = false;
692  aboveTopPath = true;
693  } else {
694  aboveTopPath = false;
695  Rectangle bounds = getPathBounds(path);
696  lastPathWasParent = !((bounds.y + bounds.height - (bounds.height / 2)) < mouseY-4);
697  if (lastPathWasParent) {
698  forceRedraw = true;
699  }
700  // we only want to re-paint the line if the line itself has moved
701  // and we are not painting a ghosted node
702  if (!showsGhostedNode && (path == currentPath && lastPathWasParent == prevPathWasParent)) {
703  if (!forceRedraw)
704  return;
705  }
706  }
707 
708  currentPath = path;
709  drawCueLine(path, lastPathWasParent, aboveTopPath);
710  }
711 
721  protected Rectangle highlightPath(TreePath path, Color color) {
722  Rectangle bounds = getPathBounds(path);
723  Graphics2D g2d = (Graphics2D) getGraphics();
724 
725  if (color != null)
726  g2d.setColor(color);
727 
728  int lineWidth = 1;
729  if (highlightStroke != null) {
730  g2d.setStroke(highlightStroke);
731  if (highlightStroke instanceof BasicStroke)
732  lineWidth = (int) ((BasicStroke) highlightStroke).getLineWidth();
733  }
734  if (path.getLastPathComponent() instanceof ExpandingNode) {
735  g2d.drawRoundRect(bounds.x-20, bounds.y, ((ExpandingNode)path.getLastPathComponent()).getInitStrWidth()+20, bounds.height, 10, 10);
736  } else {
737  g2d.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
738  }
739 
740  // grow by lineWidth as the current bounds are on the boundary
741  // of the highlight
742  bounds.grow(0, lineWidth);
743 
744  return bounds;
745  }
746 
747  /* Listener Methods */
748 
758  TreePath sourcePath,
759  TreePath parentPath,
760  int index,
761  Point p) {
762  TreePath[] paths = new TreePath[] { sourcePath };
763  fireMoveGestureRecognized(paths, parentPath, index, p);
764  }
765 
766 
767  public void fireMoveGestureRecognized(
768  TreePath[] sourcePaths,
769  TreePath parentPath,
770  int index,
771  Point p) {
772 
773  // Guaranteed to return a non-null array
774  Object[] listeners = listenerList.getListenerList();
775  CueEvent evt = null;
776 
777  // Process the listeners last to first, notifying
778  // those that are interested in this event
779  for (int i = listeners.length - 2; i >= 0; i -= 2) {
780  if (listeners[i] == CueGestureListener.class) {
781  // Lazily create the event:
782  if (evt == null)
783  evt = new CueEvent(this, sourcePaths, parentPath, index, p);
784  if (sourcePaths.length == 1) {
785  ((CueGestureListener) listeners[i + 1]).
786  moveGestureRecognized(evt);
787  } else {
788  ((CueGestureListener) listeners[i + 1]).
789  multipleMoveGestureRecognized(evt);
790  }
791  }
792  }
793  }
794 
795  public void fireMoveGestureCompleted(
796  TreePath sourcePath,
797  TreePath parentPath,
798  int index,
799  int prevIndex,
800  Point p) {
801  TreePath[] paths = new TreePath[] { sourcePath };
802  fireMoveGestureCompleted(paths, parentPath, index,prevIndex, p);
803  }
804 
811  public void fireMoveGestureCompleted(
812  TreePath[] sourcePaths,
813  TreePath parentPath,
814  int index,
815  int prevIndex,
816  Point p) {
817 
818  // Guaranteed to return a non-null array
819  Object[] listeners = listenerList.getListenerList();
820  CueEvent evt = null;
821 
822  // Process the listeners last to first, notifying
823  // those that are interested in this event
824  for (int i = listeners.length - 2; i >= 0; i -= 2) {
825  if (listeners[i] == CueGestureListener.class) {
826  // Lazily create the event:
827  if (evt == null)
828  evt = new CueEvent(this, sourcePaths, parentPath, index, prevIndex, p);
829 
830  if (sourcePaths.length == 1) {
831  ((CueGestureListener) listeners[i + 1]).
832  moveGestureCompleted(evt);
833  } else {
834  ((CueGestureListener) listeners[i + 1]).
835  multipleMoveGestureCompleted(evt);
836  }
837  }
838  }
839  }
840 
841  public void fireAddGestureRecognized(
842  TreePath sourcePath,
843  TreePath parentPath,
844  int index,
845  Point p) {
846  TreePath[] paths = new TreePath[] { sourcePath };
847  fireAddGestureRecognized(paths, parentPath, index, p);
848  }
849 
860  public void fireAddGestureRecognized(
861  TreePath[] sourcePaths,
862  TreePath parentPath,
863  int index,
864  Point p) {
865  // Guaranteed to return a non-null array
866  Object[] listeners = listenerList.getListenerList();
867  CueEvent evt = null;
868 
869  // Process the listeners last to first, notifying
870  // those that are interested in this event
871  for (int i = listeners.length - 2; i >= 0; i -= 2) {
872  if (listeners[i] == CueGestureListener.class) {
873  // Lazily create the event:
874  if (evt == null)
875  evt = new CueEvent(this, sourcePaths, parentPath, index, p);
876 
877  if (sourcePaths.length == 1) {
878  ((CueGestureListener) listeners[i + 1]).
879  addGestureRecognized(evt);
880  } else {
881  ((CueGestureListener) listeners[i + 1]).
882  multipleAddGestureRecognized(evt);
883  }
884  }
885  }
886  }
887 
888  public void fireAddGestureCompleted(
889  TreePath sourcePath,
890  TreePath parentPath,
891  int index,
892  Point p) {
893  TreePath[] paths = new TreePath[] { sourcePath };
894  fireAddGestureCompleted(paths, parentPath, index, p);
895  }
896 
907  public void fireAddGestureCompleted(
908  TreePath[] sourcePaths,
909  TreePath parentPath,
910  int index,
911  Point p) {
912  // Guaranteed to return a non-null array
913  Object[] listeners = listenerList.getListenerList();
914  CueEvent evt = null;
915 
916  // Process the listeners last to first, notifying
917  // those that are interested in this event
918  for (int i = listeners.length - 2; i >= 0; i -= 2) {
919  if (listeners[i] == CueGestureListener.class) {
920  // Lazily create the event:
921  if (evt == null)
922  evt = new CueEvent(this, sourcePaths, parentPath, index, p);
923 
924  if (sourcePaths.length == 1) {
925  ((CueGestureListener) listeners[i + 1]).
926  addGestureCompleted(evt);
927  } else {
928  ((CueGestureListener) listeners[i + 1]).
929  multipleAddGestureCompleted(evt);
930  }
931  }
932  }
933  }
934 
935  private String[] parentIds = null;
936 
937  public String[] getCueModeParentIds() {
938  return parentIds;
939  }
940 
941  public void setCueModeParentIds(TreePath[] selectedPaths) {
942  parentIds = null;
943  if (selectedPaths != null) {
944  parentIds = new String[selectedPaths.length];
945  for (int i=0; i < selectedPaths.length; i++) {
946  parentIds[i] = ((MatrixTreeNode)(((MatrixTreeNode)selectedPaths[i].getLastPathComponent())).getParent()).getAsset().getId();
947  }
948  }
949  }
950 
957  protected class CueGestureHandler extends MouseAdapter
958  implements MouseMotionListener {
959 
964  public void mouseDragged(MouseEvent evt) {}
965 
966  public void mouseReleased(MouseEvent evt) {
967  if (GUIUtilities.isRightMouseButton(evt)) {
968  if (inCueMode) {
969  TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() - cueLineOffset);
970 
971  // if we are showing the ghosted node, paint it onto
972  // the location where the mouse currently is before we
973  // paint the cue line
974  if (showsGhostedNode)
975  paintGhostedNode(evt.getX() + 5, evt.getY() - 5, path);
976  drawCueLine(path, evt.getY(), true);
977  }
978  }
979  }
980 
985  public void mousePressed(MouseEvent evt) {
986 
987  // TODO: (MM) need to make use of GUIUtilities.isRightMouseButton
988  if (GUIUtilities.isRightMouseButton(evt)) {
989  if (inCueMode) {
990  TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() - cueLineOffset);
991 
992  // if we are showing the ghosted node, paint it onto
993  // the location where the mouse currently is before we
994  // paint the cue line
995  if (showsGhostedNode)
996  paintGhostedNode(evt.getX() + 5, evt.getY() - 5, path);
997  drawCueLine(path, evt.getY(), true);
998  }
999  }
1000 
1001  // Use this listener stub in favour of mouseClicked() or mouseReleased()
1002  // as there is an instance where if a particluar node is expanded, and
1003  // a scrollbar is required, when that node is collapsed, the tree would
1004  // go into cueMode because the icon of the node above was pressed
1005 
1006  if (((CueTreeUI) getUI()).isLocationInExpandControl(evt.getX(), evt.getY()))
1007  return;
1008 
1009  if (inCueMode) {
1010  if (sourcePaths.length == 1)
1011  handleSingleSource(evt.getPoint());
1012  else
1013  handleMultipleSources(evt.getPoint());
1014  return;
1015  }
1016 
1017  TreePath[] selectedPaths = getSelectionPaths();
1018  if (selectedPaths != null && selectedPaths.length != 1) {
1019  return;
1020  }
1021 
1022  TreePath path = getPathForLocation(evt.getX(), evt.getY());
1023  if (path == null)
1024  return;
1025 
1026  if (pointTriggersMove(evt.getPoint())) {
1027  if (canMoveNode(path.getLastPathComponent())) {
1028  int index =
1029  getModel().getIndexOfChild(
1030  path.getParentPath().getLastPathComponent(),
1031  path.getLastPathComponent()
1032  );
1033 
1034  TreePath[] paths = new TreePath[] { path };
1035  fireMoveGestureRecognized(paths, path.getParentPath(), index, evt.getPoint());
1036  // if the icon was pressed, then we are in move mode
1037  requestMode = MOVE_REQUEST_MODE;
1038  startCueMode(paths);
1039  }
1040  }
1041  }
1042 
1043 
1044 
1045 
1046  protected void handleSingleSource(Point initPoint) {
1047  TreePath parentPath = null;
1048  TreePath sourcePath = sourcePaths[0];
1049 
1050  if (lastKeyPressed == KeyEvent.VK_CONTROL && (currentPath.getLastPathComponent() instanceof ExpandingNode)) {
1051  if (getCueModeParentIds() == null) {
1052  setCueModeParentIds(sourcePaths);
1053  }
1054  return;
1055  }
1056 
1057  // -1 indicates that the the parent wasn't expanded. It is up to
1058  // the CueGestureListener to determine where in the tree the node is to
1059  // be added/moved
1060  int index = -1;
1061  int indexModifier = 0;
1062  int oldIndex = 0;
1063  int newIndex = 0;
1064 
1065  if (!lastPathWasParent) {
1066  // if the last path wasn't the new parent and the current path
1067  // was expanded, then the new position is the first child of
1068  // current path
1069  if (isExpanded(currentPath)) {
1070  parentPath = currentPath;
1071  index = 0;
1072  } else {
1073  // if we are on the same branch...
1074  if ( (currentPath.getParentPath()).equals(sourcePath.getParentPath()) ) {
1075  newIndex = getModel().getIndexOfChild(
1076  currentPath.getParentPath().getLastPathComponent(),
1077  currentPath.getLastPathComponent()
1078  );
1079 
1080  oldIndex = getModel().getIndexOfChild(
1081  sourcePath.getParentPath().getLastPathComponent(),
1082  sourcePath.getLastPathComponent()
1083  );
1084 
1085  //if the new position is higher up in the tree
1086  if (newIndex < oldIndex) {
1087  indexModifier++;
1088  }
1089  }
1090 
1091  index = getModel().getIndexOfChild(
1092  currentPath.getParentPath().getLastPathComponent(),
1093  currentPath.getLastPathComponent()
1094  );
1095 
1096  if (sourcePath.getParentPath() == null) {
1097  indexModifier++;
1098  } else {
1099  oldIndex = getModel().getIndexOfChild(
1100  sourcePath.getParentPath().getLastPathComponent(),
1101  sourcePath.getLastPathComponent()
1102  );
1103  }
1104 
1105  // bug fix for #3924 Moving assets - will not sit at bottom of a list
1106  // + bug fix for #4753 moving asset in asset map downwards will move one position further
1107  if (indexModifier==0 && (index < oldIndex || !sourcePath.getParentPath().toString().equals(currentPath.getParentPath().toString())) ) {
1108  indexModifier++;
1109  }
1110 
1111  parentPath = currentPath.getParentPath();
1112  }
1113  } else {
1114  parentPath = currentPath;
1115  }
1116 
1117  index += indexModifier;
1118 
1119  // if the path was above the top path in the tree then the index
1120  // is 0
1121  if (aboveTopPath) {
1122  index = 0;
1123  }
1124 
1125  if (requestMode != ADD_REQUEST_MODE) {
1126  fireMoveGestureCompleted(sourcePath, parentPath, index, oldIndex, initPoint);
1127  } else {
1128  fireAddGestureCompleted(sourcePath, parentPath, index, initPoint);
1129  }
1130 
1131  stopCueMode();
1132  }
1133 
1134  protected void handleMultipleSources(Point initPoint) {
1135  TreePath parentPath = null;
1136 
1137  if (lastKeyPressed == KeyEvent.VK_CONTROL && (currentPath.getLastPathComponent() instanceof ExpandingNode)) {
1138  if (getCueModeParentIds() == null) {
1139  setCueModeParentIds(sourcePaths);
1140  }
1141  return;
1142  }
1143 
1144  // -1 indicates that the the parent wasn't expanded. It is up to
1145  // the CueGestureListener to determine where in the tree the node is to
1146  // be added/moved
1147  int index = -1;
1148  int oldIndex = 0;
1149 
1150  if (!lastPathWasParent) {
1151  // if the last path wasn't the new parent and the current path
1152  // was expanded, then the new position is the first child of
1153  // current path
1154  if (isExpanded(currentPath)) {
1155  parentPath = currentPath;
1156  index = 0;
1157  } else {
1158  // if we are on the same branch...
1159  if (currentPath.getParentPath() == sourcePaths[0].getParentPath()) {
1160 
1161  int newIndex = getModel().getIndexOfChild(
1162  currentPath.getParentPath().getLastPathComponent(),
1163  currentPath.getLastPathComponent()
1164  );
1165 
1166  oldIndex = getModel().getIndexOfChild(
1167  sourcePaths[0].getParentPath().getLastPathComponent(),
1168  sourcePaths[0].getLastPathComponent()
1169  );
1170 
1171  // if the old index is the same as the new index
1172  // and we are still on the same branch, then
1173  // do nothing and stop cue mode
1174  if (oldIndex == newIndex) {
1175  stopCueMode();
1176  return;
1177  }
1178  // if the node is moving down in the branch,
1179  // then we need to compensate by 1 because we
1180  // need to remove the node first, so the index
1181  // will be 1 less
1182  if (oldIndex < newIndex)
1183  index = 0;
1184  else
1185  if (!aboveTopPath)
1186  index = 1;
1187  } else {
1188  index = 1;
1189  if (sourcePaths[0].getParentPath() != null) {
1190  oldIndex = getModel().getIndexOfChild(
1191  sourcePaths[0].getParentPath().getLastPathComponent(),
1192  sourcePaths[0].getLastPathComponent()
1193  );
1194  }
1195  }
1196 
1197  index += getModel().getIndexOfChild(
1198  currentPath.getParentPath().getLastPathComponent(),
1199  currentPath.getLastPathComponent()
1200  );
1201 
1202  if (index > oldIndex) {
1203  index--;
1204  }
1205  parentPath = currentPath.getParentPath();
1206  }
1207  } else {
1208  parentPath = currentPath;
1209  }
1210 
1211  if (requestMode != ADD_REQUEST_MODE) {
1212  sourcePaths = filterMultipleNodes(sourcePaths);
1213  fireMoveGestureCompleted(sourcePaths, parentPath, index, oldIndex, initPoint);
1214  } else {
1215  fireAddGestureCompleted(sourcePaths, parentPath, index, initPoint);
1216  }
1217  stopCueMode();
1218  }
1219 
1227  protected TreePath[] filterMultipleNodes(TreePath[] sourcePaths) {
1228  return sourcePaths;
1229  }
1230 
1236  protected boolean pointTriggersMove(Point p) {
1237  return nodeIconContainsPoint(p);
1238  }
1239 
1244  public void mouseMoved(MouseEvent evt) {
1245 
1246  if (!inCueMode) {
1247  if (pointTriggersMove(evt.getPoint())) {
1248  TreePath path = getPathForLocation(evt.getX(), evt.getY());
1249  // if we can move the mode, set the mouse cursor to
1250  // the move cursor, else set it to the cant move cursor
1251  if (canMoveNode(path.getLastPathComponent()) || isNavNode(path.getLastPathComponent())) {
1252  setCursor(moveCursor);
1253  } else {
1254  setCursor(noMoveCursor);
1255  return;
1256  }
1257  } else {
1258  setCursor(Cursor.getDefaultCursor());
1259  }
1260  } else {
1261  TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() - cueLineOffset);
1262  if ((getLastKeyPressed() == KeyEvent.VK_CONTROL) && isNavNode(path.getLastPathComponent())) {
1263  if (nodeIconContainsPoint(path, new Point(evt.getX(),evt.getY()))) {
1264  setCursor(moveCursor);
1265  } else {
1266  setCursor(Cursor.getDefaultCursor());
1267  }
1268  } else {
1269  setCursor(Cursor.getDefaultCursor());
1270  }
1271 
1272  // if we are showing the ghosted node, paint it onto
1273  // the location where the mouse currently is before we
1274  // paint the cue line
1275  if (showsGhostedNode)
1276  paintGhostedNode(evt.getX() + 5, evt.getY() - 5, path);
1277  drawCueLine(path, evt.getY());
1278  }
1279  }
1280 
1281  private boolean isNavNode(Object node) {
1282  if (!(node instanceof LoadingNode) && !(node instanceof ExpandingNode)) {
1283  return false;
1284  }
1285  return true;
1286  }
1287 
1295  protected void triggerPath(
1296  final TreePath path,
1297  final int mouseY,
1298  final int triggerCount) {
1299  final Timer t = new Timer(50, null);
1300  ActionListener listener = new ActionListener() {
1301  private boolean triggered = false;
1302  private int triggeredCount = 1;
1303  public void actionPerformed(ActionEvent evt) {
1304  if (!triggered)
1305  drawCueLine(path, mouseY, true);
1306  else
1307  paintImmediately(dirtyCueBounds);
1308  if (triggeredCount++ == triggerCount) {
1309  paintImmediately(dirtyCueBounds);
1310  t.stop();
1311  }
1312  triggered = (triggered) ? false : true;
1313  }
1314  };
1315  t.addActionListener(listener);
1316  t.start();
1317  }
1318 
1319  }//end CueRequestHandler
1320 
1321  protected class CueTreeUI extends BasicTreeUI {
1322  public boolean isLocationInExpandControl(int mouseX, int mouseY) {
1323  if ((getPathForLocation(mouseX, mouseY) != null))
1324  return false;
1325  TreePath path = getClosestPathForLocation(CueTree.this, mouseX, mouseY);
1326  return isLocationInExpandControl(path, mouseX, mouseY);
1327  }
1328 
1329  protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) {
1330  MatrixTree tree = (MatrixTree)c;
1331  int nodeSize = 20;
1332  TreePath path = tree.getClosestPathForLocation(x,top-10);
1333  MatrixTreeNode node = null;
1334  if (path!=null) {
1335  node = (MatrixTreeNode)path.getLastPathComponent();
1336  }
1337 
1338  if ((node != null) && top > 10) {
1339  int topMod = 0;
1340  int bottomMod = 0;
1341  if (tree.hasNextNode(node)) {
1342  bottomMod = nodeSize-4;
1343  }
1344  if (tree.hasPreviousNode(node)) {
1345  topMod = nodeSize;
1346  }
1347  super.paintVerticalLine(g,c,x,top+topMod,bottom-bottomMod);
1348  } else {
1349  super.paintVerticalLine(g,c,x,top,bottom);
1350  }
1351  }
1352  protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
1353  MatrixTreeNode node = (MatrixTreeNode)path.getLastPathComponent();
1354  if (isNavNode(path)) {
1355  try {
1356  int nameWidth = 0;
1357  if (node instanceof ExpandingNode) {
1358  nameWidth = ((ExpandingNode)node).getInitStrWidth();
1359  }
1360 
1361  int minSize = 150;
1362  if (nameWidth < minSize) {
1363  nameWidth = minSize;
1364  }
1365  int modifier = getRightChildIndent()+5;
1366  g.setColor(MatrixLookAndFeel.PANEL_COLOUR);
1367  g.fillRoundRect((int)bounds.getX() - modifier, (int)(bounds.getY()), nameWidth+modifier, (int)(bounds.getHeight()),10,10);
1368 
1369  } catch (NullPointerException ex) {}
1370  }
1371  super.paintRow(g,clipBounds,insets,bounds,path,row,isExpanded,hasBeenExpanded,isLeaf);
1372  }
1373 
1374  protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
1375  // do not print line if this is a next or previous node
1376  if (!isNavNode(path)) {
1377  super.paintHorizontalPartOfLeg(g,clipBounds,insets,bounds,path,row,isExpanded,hasBeenExpanded,isLeaf);
1378  }
1379  }
1380 
1381  private boolean isNavNode(TreePath path) {
1382  DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1383  if (!(node instanceof ExpandingNextNode) && !(node instanceof ExpandingPreviousNode)) {
1384  return false;
1385  }
1386  return true;
1387  }
1388  }
1389 
1390  private int lastKeyPressed = -1;
1391 
1392  public void setLastKeyPressed(int keyCode) {
1393  lastKeyPressed = keyCode;
1394  }
1395 
1396  public int getLastKeyPressed() {
1397  return lastKeyPressed;
1398  }
1399 
1400  private class KeyListener extends KeyAdapter {
1401  public void keyPressed(KeyEvent evt) {
1402  setLastKeyPressed(evt.getKeyCode());
1403  }
1404 
1405  public void keyReleased(KeyEvent evt) {
1406  setLastKeyPressed(-1);
1407  }
1408  }
1409 
1415  protected boolean canMoveNode(Object node) {
1416  return true;
1417  }
1418 
1419  public static void main(String[] args) {
1420  JFrame f = new JFrame();
1421  CueTree cueTree = new CueTree();
1422 
1423  f.getContentPane().add(new JScrollPane(cueTree));
1424  f.setSize(300, 500);
1425  f.show();
1426  }
1427 }
1428