16 package net.squiz.cuetree;
19 import javax.swing.tree.*;
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;
27 import net.squiz.matrix.ui.*;
28 import net.squiz.matrix.core.*;
29 import net.squiz.matrix.matrixtree.*;
30 import net.squiz.matrix.plaf.*;
64 private Image ghostedNode = null;
65 private TreePath currentPath = null;
66 private TreePath[] sourcePaths = null;
67 private boolean showsGhostedNode =
false;
69 private Stroke highlightStroke;
71 private Stroke cueLineStroke;
72 private Color cueLineColor = Color.BLACK;
80 private boolean lastPathWasParent =
false;
86 private boolean aboveTopPath =
false;
88 private boolean moveEnabled =
true;
91 private Cursor moveCursor =
new Cursor(Cursor.HAND_CURSOR);
92 private Cursor noMoveCursor;
93 private boolean triggersPath =
true;
99 super(getDefaultTreeModel());
115 private void init() {
118 setUI(
new CueTreeUI());
120 addKeyListener(
new KeyListener());
127 private void setInvalidCursor() {
130 }
catch (Exception e) {
132 noMoveCursor = Cursor.getDefaultCursor();
173 showsGhostedNode = b;
182 return showsGhostedNode;
193 removeMouseListener(cueGestureHandler);
194 removeMouseMotionListener(cueGestureHandler);
196 addMouseListener(cueGestureHandler);
197 addMouseMotionListener(cueGestureHandler);
215 return inCueMode ? sourcePaths[0] : null;
226 cueLineStroke = stroke;
237 return cueLineStroke;
246 cueLineColor = color;
267 highlightStroke = stroke;
279 return highlightStroke;
297 noMoveCursor = cursor;
311 public boolean inCueMode() {
322 throw new IllegalArgumentException(
"path is null");
323 TreeCellRenderer renderer = getCellRenderer();
324 Object node = path.getLastPathComponent();
328 int row = ui.getRowForPath(
this, path);
329 int lsr = getLeadSelectionRow();
330 boolean hasFocus = isFocusOwner()
333 return renderer.getTreeCellRendererComponent(
336 isPathSelected(path),
338 getModel().isLeaf(node),
354 if (paths.length == 1)
357 Rectangle bounds =
new Rectangle();
359 for (
int i = 0; i < paths.length; i++) {
360 if (paths[i] != null)
375 throw new NullPointerException(
"Cannot create Ghosted Node: " +
376 " component is null");
380 c.setSize(bounds.width, bounds.height);
382 BufferedImage image =
new BufferedImage(
385 BufferedImage.TYPE_INT_ARGB_PRE
388 Graphics2D g2d = (Graphics2D) image.createGraphics();
389 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.3f));
404 BufferedImage image =
new BufferedImage(
407 BufferedImage.TYPE_INT_ARGB_PRE
409 Graphics2D g2d = (Graphics2D) image.createGraphics();
413 for (
int i = 0; i < paths.length; i++) {
416 g2d.drawImage(pathImage, pathBounds.x, pathBounds.y, null);
430 if (ghostedNode == null)
432 Graphics g = getGraphics();
433 g.drawImage(ghostedNode, x, y,
this);
435 dirtyGhostBounds.setRect(x, y, bounds.width, bounds.height);
452 throw new IllegalArgumentException(
"path is null");
458 if (!(c instanceof JLabel))
464 Icon icon = ((JLabel) c).getIcon();
468 bounds.setSize(icon.getIconWidth(), icon.getIconHeight());
490 return (bounds.contains(point));
501 TreePath path = getPathForLocation(point.x, point.y);
525 fireAddGestureRecognized(paths, null, -1, initPoint);
528 drawCueLine(getClosestPathForLocation(initPoint.x, initPoint.y), initPoint.y,
true);
542 setCursor(Cursor.getDefaultCursor());
552 paintImmediately(dirtyCueBounds);
577 protected void drawCueLine(TreePath path,
boolean pathIsNewParent,
boolean aboveTopPath) {
579 paintImmediately(dirtyCueBounds);
581 Graphics2D g2d = (Graphics2D) getGraphics();
583 int x1 = 0, x2 = 0, y = 0, ExpandingNodeModifier = 0;
585 boolean updateNode =
false;
586 TreeNode node = (TreeNode)path.getLastPathComponent();
587 if (pathIsNewParent) {
589 if (node instanceof ExpandingNextNode) {
590 if (!((ExpandingNode) node).usingCueModeName()) {
591 ((ExpandingNode) node).useCueModeName();
594 ExpandingNodeModifier = ((ExpandingNode) node).getInitStrWidth();
595 }
else if (node instanceof ExpandingPreviousNode) {
596 if (!((ExpandingNode) node).usingCueModeName()) {
597 ((ExpandingNode) node).useCueModeName();
600 ExpandingNodeModifier = ((ExpandingNode) node).getInitStrWidth();
604 ((DefaultTreeModel) getModel()).nodeChanged(node);
607 dirtyCueBounds =
highlightPath(path, UIManager.getColor(
"CueLine.stroke"));
608 if (ExpandingNodeModifier > 0) {
609 bounds.width = ExpandingNodeModifier;
611 x1 = bounds.x + bounds.width;
612 y = bounds.y + (bounds.height / 2);
616 if ((node instanceof ExpandingNextNode) || ((MatrixTreeNode)node.getParent()).hasNextNode()) {
617 if (!(node instanceof ExpandingNextNode)) {
618 node = node.getParent().getChildAt(node.getParent().getChildCount()-1);
620 if (((ExpandingNode)node).usingCueModeName()) {
621 ((ExpandingNode) node).setName(((ExpandingNode) node).getName());
622 ((DefaultTreeModel) getModel()).nodeChanged(node);
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);
632 if ((node instanceof ExpandingPreviousNode)) {
634 if (((ExpandingNode)node).usingCueModeName()) {
635 ((ExpandingNode) node).setName(((ExpandingNode) node).getName());
636 ((DefaultTreeModel) getModel()).nodeChanged(node);
640 if (isExpanded(path) && getModel().isLeaf(path.getLastPathComponent())) {
646 Object child = getModel().getChild(path.getLastPathComponent(), 0);
647 Rectangle childBounds =
getPathBounds(path.pathByAddingChild(child));
654 y = bounds.y + bounds.height;
657 x2 = getX() + getWidth();
660 if (cueLineStroke != null) {
661 g2d.setStroke(cueLineStroke);
662 if (highlightStroke instanceof BasicStroke)
663 lineWidth = (int) ((BasicStroke) highlightStroke).getLineWidth();
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);
687 private void drawCueLine(TreePath path,
int mouseY,
boolean forceRedraw) {
688 boolean prevPathWasParent = lastPathWasParent;
691 lastPathWasParent =
false;
694 aboveTopPath =
false;
696 lastPathWasParent = !((bounds.y + bounds.height - (bounds.height / 2)) < mouseY-4);
697 if (lastPathWasParent) {
702 if (!showsGhostedNode && (path == currentPath && lastPathWasParent == prevPathWasParent)) {
709 drawCueLine(path, lastPathWasParent, aboveTopPath);
723 Graphics2D g2d = (Graphics2D) getGraphics();
729 if (highlightStroke != null) {
730 g2d.setStroke(highlightStroke);
731 if (highlightStroke instanceof BasicStroke)
732 lineWidth = (int) ((BasicStroke) highlightStroke).getLineWidth();
734 if (path.getLastPathComponent() instanceof ExpandingNode) {
735 g2d.drawRoundRect(bounds.x-20, bounds.y, ((ExpandingNode)path.getLastPathComponent()).getInitStrWidth()+20, bounds.height, 10, 10);
737 g2d.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
742 bounds.grow(0, lineWidth);
762 TreePath[] paths =
new TreePath[] { sourcePath };
768 TreePath[] sourcePaths,
774 Object[] listeners = listenerList.getListenerList();
779 for (
int i = listeners.length - 2; i >= 0; i -= 2) {
783 evt =
new CueEvent(
this, sourcePaths, parentPath, index, p);
784 if (sourcePaths.length == 1) {
786 moveGestureRecognized(evt);
788 ((CueGestureListener) listeners[i + 1]).
789 multipleMoveGestureRecognized(evt);
795 public void fireMoveGestureCompleted(
801 TreePath[] paths =
new TreePath[] { sourcePath };
802 fireMoveGestureCompleted(paths, parentPath, index,prevIndex, p);
811 public void fireMoveGestureCompleted(
812 TreePath[] sourcePaths,
819 Object[] listeners = listenerList.getListenerList();
824 for (
int i = listeners.length - 2; i >= 0; i -= 2) {
828 evt =
new CueEvent(
this, sourcePaths, parentPath, index, prevIndex, p);
830 if (sourcePaths.length == 1) {
832 moveGestureCompleted(evt);
835 multipleMoveGestureCompleted(evt);
841 public void fireAddGestureRecognized(
846 TreePath[] paths =
new TreePath[] { sourcePath };
847 fireAddGestureRecognized(paths, parentPath, index, p);
860 public void fireAddGestureRecognized(
861 TreePath[] sourcePaths,
866 Object[] listeners = listenerList.getListenerList();
871 for (
int i = listeners.length - 2; i >= 0; i -= 2) {
875 evt =
new CueEvent(
this, sourcePaths, parentPath, index, p);
877 if (sourcePaths.length == 1) {
879 addGestureRecognized(evt);
882 multipleAddGestureRecognized(evt);
888 public void fireAddGestureCompleted(
893 TreePath[] paths =
new TreePath[] { sourcePath };
894 fireAddGestureCompleted(paths, parentPath, index, p);
907 public void fireAddGestureCompleted(
908 TreePath[] sourcePaths,
913 Object[] listeners = listenerList.getListenerList();
918 for (
int i = listeners.length - 2; i >= 0; i -= 2) {
922 evt =
new CueEvent(
this, sourcePaths, parentPath, index, p);
924 if (sourcePaths.length == 1) {
926 addGestureCompleted(evt);
929 multipleAddGestureCompleted(evt);
935 private String[] parentIds = null;
937 public String[] getCueModeParentIds() {
941 public void setCueModeParentIds(TreePath[] selectedPaths) {
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();
958 implements MouseMotionListener {
966 public void mouseReleased(MouseEvent evt) {
967 if (GUIUtilities.isRightMouseButton(evt)) {
969 TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() -
cueLineOffset);
974 if (showsGhostedNode)
988 if (GUIUtilities.isRightMouseButton(evt)) {
990 TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() -
cueLineOffset);
995 if (showsGhostedNode)
1006 if (((
CueTreeUI) getUI()).isLocationInExpandControl(evt.getX(), evt.getY()))
1010 if (sourcePaths.length == 1)
1011 handleSingleSource(evt.getPoint());
1013 handleMultipleSources(evt.getPoint());
1017 TreePath[] selectedPaths = getSelectionPaths();
1018 if (selectedPaths != null && selectedPaths.length != 1) {
1022 TreePath path = getPathForLocation(evt.getX(), evt.getY());
1029 getModel().getIndexOfChild(
1030 path.getParentPath().getLastPathComponent(),
1031 path.getLastPathComponent()
1034 TreePath[] paths =
new TreePath[] { path };
1046 protected void handleSingleSource(Point initPoint) {
1047 TreePath parentPath = null;
1048 TreePath sourcePath = sourcePaths[0];
1050 if (lastKeyPressed == KeyEvent.VK_CONTROL && (currentPath.getLastPathComponent() instanceof ExpandingNode)) {
1051 if (getCueModeParentIds() == null) {
1052 setCueModeParentIds(sourcePaths);
1061 int indexModifier = 0;
1065 if (!lastPathWasParent) {
1069 if (isExpanded(currentPath)) {
1070 parentPath = currentPath;
1074 if ( (currentPath.getParentPath()).equals(sourcePath.getParentPath()) ) {
1075 newIndex = getModel().getIndexOfChild(
1076 currentPath.getParentPath().getLastPathComponent(),
1077 currentPath.getLastPathComponent()
1080 oldIndex = getModel().getIndexOfChild(
1081 sourcePath.getParentPath().getLastPathComponent(),
1082 sourcePath.getLastPathComponent()
1086 if (newIndex < oldIndex) {
1091 index = getModel().getIndexOfChild(
1092 currentPath.getParentPath().getLastPathComponent(),
1093 currentPath.getLastPathComponent()
1096 if (sourcePath.getParentPath() == null) {
1099 oldIndex = getModel().getIndexOfChild(
1100 sourcePath.getParentPath().getLastPathComponent(),
1101 sourcePath.getLastPathComponent()
1107 if (indexModifier==0 && (index < oldIndex || !sourcePath.getParentPath().toString().equals(currentPath.getParentPath().toString())) ) {
1111 parentPath = currentPath.getParentPath();
1114 parentPath = currentPath;
1117 index += indexModifier;
1126 fireMoveGestureCompleted(sourcePath, parentPath, index, oldIndex, initPoint);
1128 fireAddGestureCompleted(sourcePath, parentPath, index, initPoint);
1134 protected void handleMultipleSources(Point initPoint) {
1135 TreePath parentPath = null;
1137 if (lastKeyPressed == KeyEvent.VK_CONTROL && (currentPath.getLastPathComponent() instanceof ExpandingNode)) {
1138 if (getCueModeParentIds() == null) {
1139 setCueModeParentIds(sourcePaths);
1150 if (!lastPathWasParent) {
1154 if (isExpanded(currentPath)) {
1155 parentPath = currentPath;
1159 if (currentPath.getParentPath() == sourcePaths[0].getParentPath()) {
1161 int newIndex = getModel().getIndexOfChild(
1162 currentPath.getParentPath().getLastPathComponent(),
1163 currentPath.getLastPathComponent()
1166 oldIndex = getModel().getIndexOfChild(
1167 sourcePaths[0].getParentPath().getLastPathComponent(),
1168 sourcePaths[0].getLastPathComponent()
1174 if (oldIndex == newIndex) {
1182 if (oldIndex < newIndex)
1189 if (sourcePaths[0].getParentPath() != null) {
1190 oldIndex = getModel().getIndexOfChild(
1191 sourcePaths[0].getParentPath().getLastPathComponent(),
1192 sourcePaths[0].getLastPathComponent()
1197 index += getModel().getIndexOfChild(
1198 currentPath.getParentPath().getLastPathComponent(),
1199 currentPath.getLastPathComponent()
1202 if (index > oldIndex) {
1205 parentPath = currentPath.getParentPath();
1208 parentPath = currentPath;
1213 fireMoveGestureCompleted(sourcePaths, parentPath, index, oldIndex, initPoint);
1215 fireAddGestureCompleted(sourcePaths, parentPath, index, initPoint);
1248 TreePath path = getPathForLocation(evt.getX(), evt.getY());
1251 if (
canMoveNode(path.getLastPathComponent()) || isNavNode(path.getLastPathComponent())) {
1252 setCursor(moveCursor);
1254 setCursor(noMoveCursor);
1258 setCursor(Cursor.getDefaultCursor());
1261 TreePath path = getClosestPathForLocation(evt.getX(), evt.getY() -
cueLineOffset);
1262 if ((getLastKeyPressed() == KeyEvent.VK_CONTROL) && isNavNode(path.getLastPathComponent())) {
1264 setCursor(moveCursor);
1266 setCursor(Cursor.getDefaultCursor());
1269 setCursor(Cursor.getDefaultCursor());
1275 if (showsGhostedNode)
1281 private boolean isNavNode(Object node) {
1282 if (!(node instanceof LoadingNode) && !(node instanceof ExpandingNode)) {
1296 final TreePath path,
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) {
1307 paintImmediately(dirtyCueBounds);
1308 if (triggeredCount++ == triggerCount) {
1309 paintImmediately(dirtyCueBounds);
1312 triggered = (triggered) ?
false :
true;
1315 t.addActionListener(listener);
1322 public boolean isLocationInExpandControl(
int mouseX,
int mouseY) {
1323 if ((getPathForLocation(mouseX, mouseY) != null))
1325 TreePath path = getClosestPathForLocation(
CueTree.this, mouseX, mouseY);
1326 return isLocationInExpandControl(path, mouseX, mouseY);
1329 protected void paintVerticalLine(Graphics g, JComponent c,
int x,
int top,
int bottom) {
1330 MatrixTree tree = (MatrixTree)c;
1332 TreePath path = tree.getClosestPathForLocation(x,top-10);
1333 MatrixTreeNode node = null;
1335 node = (MatrixTreeNode)path.getLastPathComponent();
1338 if ((node != null) && top > 10) {
1341 if (tree.hasNextNode(node)) {
1342 bottomMod = nodeSize-4;
1344 if (tree.hasPreviousNode(node)) {
1347 super.paintVerticalLine(g,c,x,top+topMod,bottom-bottomMod);
1349 super.paintVerticalLine(g,c,x,top,bottom);
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)) {
1357 if (node instanceof ExpandingNode) {
1358 nameWidth = ((ExpandingNode)node).getInitStrWidth();
1362 if (nameWidth < minSize) {
1363 nameWidth = minSize;
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);
1369 }
catch (NullPointerException ex) {}
1371 super.paintRow(g,clipBounds,insets,bounds,path,row,isExpanded,hasBeenExpanded,isLeaf);
1374 protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path,
int row,
boolean isExpanded,
boolean hasBeenExpanded,
boolean isLeaf) {
1376 if (!isNavNode(path)) {
1377 super.paintHorizontalPartOfLeg(g,clipBounds,insets,bounds,path,row,isExpanded,hasBeenExpanded,isLeaf);
1381 private boolean isNavNode(TreePath path) {
1382 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1383 if (!(node instanceof ExpandingNextNode) && !(node instanceof ExpandingPreviousNode)) {
1390 private int lastKeyPressed = -1;
1392 public void setLastKeyPressed(
int keyCode) {
1393 lastKeyPressed = keyCode;
1396 public int getLastKeyPressed() {
1397 return lastKeyPressed;
1400 private class KeyListener
extends KeyAdapter {
1401 public void keyPressed(KeyEvent evt) {
1402 setLastKeyPressed(evt.getKeyCode());
1405 public void keyReleased(KeyEvent evt) {
1406 setLastKeyPressed(-1);
1419 public static void main(String[] args) {
1420 JFrame f =
new JFrame();
1423 f.getContentPane().add(
new JScrollPane(cueTree));
1424 f.setSize(300, 500);