Browse Source

Add initial draft of TreeTable.

pull/198/head
weisj 5 years ago
parent
commit
2e8b9c53dd
  1. 121
      core/src/main/java/com/github/weisj/darklaf/components/treetable/DefaultTreeTableCellRenderer.java
  2. 106
      core/src/main/java/com/github/weisj/darklaf/components/treetable/JTreeTable.java
  3. 103
      core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableModelAdapter.java
  4. 30
      core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableSelectionModel.java
  5. 40
      core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableTree.java
  6. 119
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/AbstractTreeTableModel.java
  7. 95
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableModel.java
  8. 79
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableNode.java
  9. 99
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableSelectionModel.java
  10. 166
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DelegatingListSelectionModel.java
  11. 80
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/TreeTableModel.java
  12. 88
      core/src/main/java/com/github/weisj/darklaf/components/treetable/model/TreeTableNode.java
  13. 41
      core/src/main/java/com/github/weisj/darklaf/ui/table/renderer/DarkTableCellRendererDelegate.java
  14. 399
      core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java
  15. 17
      core/src/test/java/ui/tree/TreeDemo.java
  16. 96
      core/src/test/java/ui/treetable/TreeTableDemo.java
  17. 9
      utils/src/main/java/com/github/weisj/darklaf/util/PropertyUtil.java

121
core/src/main/java/com/github/weisj/darklaf/components/treetable/DefaultTreeTableCellRenderer.java

@ -0,0 +1,121 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import com.github.weisj.darklaf.ui.tree.DarkTreeUI;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class DefaultTreeTableCellRenderer extends JComponent implements TableCellRenderer {
private final JTreeTable treeTable;
private final RendererTree rendererTree = new RendererTree();
private int paintingRow;
public DefaultTreeTableCellRenderer(final JTreeTable treeTable, final TreeModel model) {
this.treeTable = treeTable;
rendererTree.setRowHeight(rendererTree.getRowHeight());
rendererTree.setModel(model);
rendererTree.setBounds(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
protected void paintComponent(final Graphics g) {
DarkTreeUI ui = DarkUIUtil.getUIOfType(rendererTree.getUI(), DarkTreeUI.class);
if (ui != null) {
ui.paintRow(g, paintingRow);
}
}
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
final boolean hasFocus, final int row, final int column) {
paintingRow = row;
rendererTree.setFocus(hasFocus || table.hasFocus());
rendererTree.setSelectable(!hasFocus);
return this;
}
public TreeTableTree getTree() {
return rendererTree;
}
protected class RendererTree extends TreeTableTree {
private boolean focus;
private boolean selectable;
public void setRowHeight(final int rowHeight) {
if (rowHeight > 0) {
super.setRowHeight(rowHeight);
if (treeTable != null && treeTable.getRowHeight() != rowHeight) {
treeTable.setRowHeight(getRowHeight());
}
}
}
@Override
public void repaint(final int x, final int y, final int width, final int height) {
treeTable.repaint(x, y, treeTable.getColumnModel().getColumn(0).getWidth(), height);
}
@Override
public boolean isRowSelected(final int row) {
return selectable && super.isRowSelected(row);
}
@Override
public boolean isPathSelected(final TreePath path) {
return selectable && super.isPathSelected(path);
}
@Override
public void scrollRectToVisible(final Rectangle aRect) {
treeTable.scrollRectToVisible(aRect);
}
public void setFocus(final boolean focus) {
this.focus = focus;
}
public void setSelectable(final boolean selectable) {
this.selectable = selectable;
}
@Override
public boolean hasFocus() {
return focus;
}
@Override
public boolean isFocusOwner() {
return super.hasFocus();
}
}
}

106
core/src/main/java/com/github/weisj/darklaf/components/treetable/JTreeTable.java

@ -0,0 +1,106 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import com.github.weisj.darklaf.components.treetable.model.AbstractTreeTableModel;
import com.github.weisj.darklaf.components.treetable.model.DefaultTreeTableSelectionModel;
import com.github.weisj.darklaf.components.treetable.model.TreeTableModel;
import com.github.weisj.darklaf.ui.cell.hint.CellHintPopupListener;
import com.github.weisj.darklaf.ui.tree.DarkTreeUI;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class JTreeTable extends JTable implements TreeSelectionListener {
private final TreeTableTree tree;
public JTreeTable(final AbstractTreeTableModel treeTableModel) {
DefaultTreeTableCellRenderer treeCellRenderer = new DefaultTreeTableCellRenderer(this, treeTableModel);
tree = treeCellRenderer.getTree();
add(tree);
tree.putClientProperty(DarkTreeUI.KEY_IS_TABLE_TREE, true);
DefaultTreeTableSelectionModel selectionModel = new DefaultTreeTableSelectionModel(tree);
tree.setSelectionModel(selectionModel);
setSelectionModel(selectionModel);
tree.addTreeSelectionListener(this);
setDefaultRenderer(TreeTableModel.class, treeCellRenderer);
super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
setShowHorizontalLines(false);
}
@Override
public void doLayout() {
super.doLayout();
int grid = getShowVerticalLines() ? getIntercellSpacing().width : 0;
tree.setBounds(0, 0, getColumnModel().getColumn(0).getWidth() - grid, getHeight());
}
@Override
public void repaint(final Rectangle r) {
super.repaint(r);
SwingUtilities.invokeLater(() -> {
DarkTreeUI ui = DarkUIUtil.getUIOfType(tree.getUI(), DarkTreeUI.class);
if (ui != null) {
CellHintPopupListener<JTree, ?> listener = ui.getPopupListener();
if (listener != null) listener.repaint();
}
});
}
@Override
public int getComponentCount() {
return 0;
}
@Override
protected void processEvent(final AWTEvent e) {
if (e instanceof InputEvent) {
if (e instanceof KeyEvent) {
if (getColumnModel().getSelectionModel().getLeadSelectionIndex() == 0
&& !((KeyEvent) e).isShiftDown()) {
tree.processEvent(e);
}
}
if (((InputEvent) e).isConsumed()) return;
}
super.processEvent(e);
}
@Override
public void valueChanged(final TreeSelectionEvent e) {
repaint(0, 0, getColumnModel().getColumn(0).getWidth(), getHeight());
}
}

103
core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableModelAdapter.java

@ -0,0 +1,103 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import com.github.weisj.darklaf.components.treetable.model.TreeTableModel;
public class TreeTableModelAdapter extends AbstractTableModel {
private final JTree tree;
private final TreeTableModel treeTableModel;
public TreeTableModelAdapter(final TreeTableModel treeTableModel, final JTree tree) {
this.tree = tree;
this.treeTableModel = treeTableModel;
tree.addTreeExpansionListener(new TreeExpansionListener() {
public void treeExpanded(final TreeExpansionEvent event) {
TreePath path = event.getPath();
int start = tree.getRowForPath(path);
int length = ((TreeNode) path.getLastPathComponent()).getChildCount();
int selection = tree.getLeadSelectionRow();
fireTableRowsInserted(start, start + length);
tree.setSelectionRow(selection);
}
public void treeCollapsed(final TreeExpansionEvent event) {
TreePath path = event.getPath();
int start = tree.getRowForPath(path);
int length = ((TreeNode) path.getLastPathComponent()).getChildCount();
int selection = tree.getLeadSelectionRow();
fireTableRowsDeleted(start, start + length);
tree.setSelectionRow(selection);
}
});
}
@Override
public void fireTableRowsDeleted(final int firstRow, final int lastRow) {
super.fireTableRowsDeleted(firstRow, lastRow);
}
public int getColumnCount() {
return treeTableModel.getColumnCount();
}
public String getColumnName(final int column) {
return treeTableModel.getColumnName(column);
}
public Class<?> getColumnClass(final int column) {
return treeTableModel.getColumnClass(column);
}
public int getRowCount() {
return tree.getRowCount();
}
protected Object nodeForRow(final int row) {
TreePath treePath = tree.getPathForRow(row);
return treePath.getLastPathComponent();
}
public Object getValueAt(final int row, final int column) {
return treeTableModel.getValueAt(nodeForRow(row), column);
}
public boolean isCellEditable(final int row, final int column) {
return treeTableModel.isCellEditable(nodeForRow(row), column);
}
public void setValueAt(final Object value, final int row, final int column) {
treeTableModel.setValueAt(value, nodeForRow(row), column);
}
}

30
core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableSelectionModel.java

@ -0,0 +1,30 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable;
import javax.swing.*;
import javax.swing.tree.TreeSelectionModel;
public interface TreeTableSelectionModel extends TreeSelectionModel, ListSelectionModel {}

40
core/src/main/java/com/github/weisj/darklaf/components/treetable/TreeTableTree.java

@ -0,0 +1,40 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable;
import java.awt.*;
import javax.swing.*;
public class TreeTableTree extends JTree {
@Override
public void processEvent(final AWTEvent e) {
super.processEvent(e);
}
@Override
public void paint(final Graphics g) {}
}

119
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/AbstractTreeTableModel.java

@ -0,0 +1,119 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
public abstract class AbstractTreeTableModel implements TreeTableModel {
protected TreeTableNode root;
protected EventListenerList listenerList = new EventListenerList();
private static final int CHANGED = 0;
private static final int INSERTED = 1;
private static final int REMOVED = 2;
private static final int STRUCTURE_CHANGED = 3;
public AbstractTreeTableModel(final TreeTableNode root) {
this.root = root;
}
@Override
public TreeTableNode getRoot() {
return root;
}
@Override
public boolean isLeaf(final Object node) {
return getChildCount(node) == 0;
}
@Override
public void valueForPathChanged(final TreePath path, final Object newValue) {}
@Override
public int getIndexOfChild(final Object parent, final Object child) {
return 0;
}
@Override
public void addTreeModelListener(final TreeModelListener l) {
listenerList.add(TreeModelListener.class, l);
}
@Override
public void removeTreeModelListener(final TreeModelListener l) {
listenerList.remove(TreeModelListener.class, l);
}
private void fireTreeNode(final int changeType, final Object source, final Object[] path, final int[] childIndices,
final Object[] children) {
Object[] listeners = listenerList.getListenerList();
TreeModelEvent e = new TreeModelEvent(source, path, childIndices, children);
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == TreeModelListener.class) {
switch (changeType) {
case CHANGED :
((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
break;
case INSERTED :
((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
break;
case REMOVED :
((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
break;
case STRUCTURE_CHANGED :
((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
break;
default :
break;
}
}
}
}
protected void fireTreeNodesChanged(final Object source, final Object[] path,
final int[] childIndices, final Object[] children) {
fireTreeNode(CHANGED, source, path, childIndices, children);
}
protected void fireTreeNodesInserted(final Object source, final Object[] path,
final int[] childIndices, final Object[] children) {
fireTreeNode(INSERTED, source, path, childIndices, children);
}
protected void fireTreeNodesRemoved(final Object source, final Object[] path,
final int[] childIndices, final Object[] children) {
fireTreeNode(REMOVED, source, path, childIndices, children);
}
protected void fireTreeStructureChanged(final Object source, final Object[] path,
final int[] childIndices, final Object[] children) {
fireTreeNode(STRUCTURE_CHANGED, source, path, childIndices, children);
}
}

95
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableModel.java

@ -0,0 +1,95 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class DefaultTreeTableModel extends AbstractTreeTableModel {
private final String[] headers;
private Class<?>[] columnClasses;
public DefaultTreeTableModel(final TreeTableNode root, final String[] headers) {
super(root);
this.headers = headers;
}
public void setColumnClasses(final Class<?>[] columnClasses) {
this.columnClasses = columnClasses;
}
@Override
public int getColumnCount() {
return headers.length;
}
@Override
public String getColumnName(final int column) {
return headers[column];
}
@Override
public Class<?> getColumnClass(final int column) {
if (column == 0) {
return TreeTableModel.class;
} else if (columnClasses != null && column < columnClasses.length) {
return columnClasses[column];
} else {
return Object.class;
}
}
@Override
public Object getValueAt(final Object node, final int column) {
TreeTableNode treeTableNode = DarkUIUtil.nullableCast(TreeTableNode.class, node);
if (treeTableNode == null) return null;
return treeTableNode.getValueAt(column);
}
@Override
public boolean isCellEditable(final Object node, final int column) {
return column != 0;
}
@Override
public void setValueAt(final Object aValue, final Object node, final int column) {
TreeTableNode treeTableNode = DarkUIUtil.nullableCast(TreeTableNode.class, node);
if (treeTableNode != null) {
treeTableNode.setValueAt(column, aValue);
}
}
@Override
public Object getChild(final Object parent, final int index) {
TreeTableNode treeTableNode = DarkUIUtil.nullableCast(TreeTableNode.class, parent);
return treeTableNode != null ? treeTableNode.getChildAt(index) : null;
}
@Override
public int getChildCount(final Object parent) {
TreeTableNode treeTableNode = DarkUIUtil.nullableCast(TreeTableNode.class, parent);
return treeTableNode != null ? treeTableNode.getChildCount() : 0;
}
}

79
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableNode.java

@ -0,0 +1,79 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DefaultTreeTableNode implements TreeTableNode {
private final List<TreeTableNode> children;
private final TreeTableNode parent;
private final List<Object> columns;
public DefaultTreeTableNode(final TreeTableNode parent,
final Object[] columns) {
this(parent, Arrays.asList(columns));
}
public DefaultTreeTableNode(final TreeTableNode parent, final List<Object> columns) {
this.parent = parent;
this.columns = columns;
this.children = new ArrayList<>();
}
@Override
public List<TreeTableNode> getChildren() {
return children;
}
@Override
public TreeTableNode getParent() {
return parent;
}
@Override
public boolean getAllowsChildren() {
return true;
}
public void addChild(final TreeTableNode child) {
children.add(child);
}
public void removeChild(final TreeTableNode child) {
children.remove(child);
}
public String toString() {
return getTreeValue().toString();
}
@Override
public List<Object> getColumns() {
return columns;
}
}

99
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DefaultTreeTableSelectionModel.java

@ -0,0 +1,99 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import javax.swing.*;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
public class DefaultTreeTableSelectionModel extends DefaultTreeSelectionModel
implements DelegatingListSelectionModel {
private final JTree tree;
private final ListSelectionModel bridgeModel;
public DefaultTreeTableSelectionModel(final JTree tree) {
this.tree = tree;
bridgeModel = new TreeTableListSelectionModel();
}
@Override
public ListSelectionModel getListDelegate() {
return bridgeModel;
}
protected class TreeTableListSelectionModel implements DelegatingListSelectionModel {
@Override
public ListSelectionModel getListDelegate() {
return listSelectionModel;
}
@Override
public void setSelectionInterval(final int index0, final int index1) {
setSelectionPaths(getTreePaths(index0, index1));
}
@Override
public void addSelectionInterval(final int index0, final int index1) {
addSelectionPaths(getTreePaths(index0, index1));
}
protected TreePath[] getTreePaths(final int index0, final int index1) {
int start = Math.min(index0, index1);
int end = Math.max(index0, index1);
TreePath[] paths = new TreePath[end - start + 1];
for (int i = start; i <= end; i++) {
paths[i - start] = tree.getPathForRow(i);
}
return paths;
}
@Override
public void removeSelectionInterval(final int index0, final int index1) {
removeSelectionPaths(getTreePaths(index0, index1));
}
@Override
public void setLeadSelectionIndex(final int index) {
tree.setLeadSelectionPath(tree.getPathForRow(index));
}
@Override
public int getLeadSelectionIndex() {
return getLeadSelectionRow();
}
@Override
public void setAnchorSelectionIndex(final int index) {
tree.setAnchorSelectionPath(tree.getPathForRow(index));
}
@Override
public int getAnchorSelectionIndex() {
return tree.getRowForPath(tree.getAnchorSelectionPath());
}
}
}

166
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/DelegatingListSelectionModel.java

@ -0,0 +1,166 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import javax.swing.*;
import javax.swing.event.ListSelectionListener;
public interface DelegatingListSelectionModel extends ListSelectionModel {
ListSelectionModel getListDelegate();
@Override
default void setSelectionInterval(final int index0, final int index1) {
getListDelegate().setSelectionInterval(index0, index1);
}
@Override
default void addSelectionInterval(final int index0, final int index1) {
getListDelegate().addSelectionInterval(index0, index1);
}
@Override
default void removeSelectionInterval(final int index0, final int index1) {
getListDelegate().removeSelectionInterval(index0, index1);
}
@Override
default int getMinSelectionIndex() {
return getListDelegate().getMinSelectionIndex();
}
@Override
default int getMaxSelectionIndex() {
return getListDelegate().getMaxSelectionIndex();
}
@Override
default boolean isSelectedIndex(final int index) {
return getListDelegate().isSelectedIndex(index);
}
@Override
default int getAnchorSelectionIndex() {
return getListDelegate().getAnchorSelectionIndex();
}
@Override
default void setAnchorSelectionIndex(final int index) {
getListDelegate().setAnchorSelectionIndex(index);
}
@Override
default int getLeadSelectionIndex() {
return getListDelegate().getLeadSelectionIndex();
}
@Override
default void setLeadSelectionIndex(final int index) {
getListDelegate().setLeadSelectionIndex(index);
}
@Override
default void clearSelection() {
getListDelegate().clearSelection();
}
@Override
default boolean isSelectionEmpty() {
return getListDelegate().isSelectionEmpty();
}
@Override
default void insertIndexInterval(final int index, final int length, final boolean before) {
getListDelegate().insertIndexInterval(index, length, before);
}
@Override
default void removeIndexInterval(final int index0, final int index1) {
getListDelegate().removeIndexInterval(index0, index1);
}
@Override
default void setValueIsAdjusting(final boolean valueIsAdjusting) {
getListDelegate().setValueIsAdjusting(valueIsAdjusting);
}
@Override
default boolean getValueIsAdjusting() {
return getListDelegate().getValueIsAdjusting();
}
@Override
default void setSelectionMode(final int selectionMode) {
getListDelegate().setSelectionMode(selectionMode);
}
@Override
default int getSelectionMode() {
return getListDelegate().getSelectionMode();
}
@Override
default void addListSelectionListener(final ListSelectionListener x) {
getListDelegate().addListSelectionListener(x);
}
@Override
default void removeListSelectionListener(final ListSelectionListener x) {
getListDelegate().removeListSelectionListener(x);
}
default int[] getSelectedIndices() {
int iMin = getMinSelectionIndex();
int iMax = getMaxSelectionIndex();
if ((iMin < 0) || (iMax < 0)) {
return new int[0];
}
int[] rvTmp = new int[1 + (iMax - iMin)];
int n = 0;
for (int i = iMin; i <= iMax; i++) {
if (isSelectedIndex(i)) {
rvTmp[n++] = i;
}
}
int[] rv = new int[n];
System.arraycopy(rvTmp, 0, rv, 0, n);
return rv;
}
default int getSelectedItemsCount() {
int iMin = getMinSelectionIndex();
int iMax = getMaxSelectionIndex();
int count = 0;
for (int i = iMin; i <= iMax; i++) {
if (isSelectedIndex(i)) {
count++;
}
}
return count;
}
}

80
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/TreeTableModel.java

@ -0,0 +1,80 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import javax.swing.tree.TreeModel;
public interface TreeTableModel extends TreeModel {
/**
* Returns the number of available columns.
*
* @return Number of Columns
*/
int getColumnCount();
/**
* Returns the column name.
*
* @param column Column number
* @return Column name
*/
String getColumnName(int column);
/**
* Returns the type (class) of a column.
*
* @param column Column number
* @return Class
*/
Class<?> getColumnClass(int column);
/**
* Returns the value of a node in a column.
*
* @param node Node
* @param column Column number
* @return Value of the node in the column
*/
Object getValueAt(Object node, int column);
/**
* Check if a cell of a node in one column is editable.
*
* @param node Node
* @param column Column number
* @return true/false
*/
boolean isCellEditable(Object node, int column);
/**
* Sets a value for a node in one column.
*
* @param aValue New value
* @param node Node
* @param column Column number
*/
void setValueAt(Object aValue, Object node, int column);
}

88
core/src/main/java/com/github/weisj/darklaf/components/treetable/model/TreeTableNode.java

@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.darklaf.components.treetable.model;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.tree.TreeNode;
public interface TreeTableNode extends TreeNode {
List<TreeTableNode> getChildren();
@Override
default Enumeration<? extends TreeTableNode> children() {
return Collections.enumeration(getChildren());
}
@Override
TreeTableNode getParent();
@Override
default int getIndex(final TreeNode node) {
if (!(node instanceof TreeTableNode)) return -1;
return getIndex((TreeTableNode) node);
}
default int getIndex(final TreeTableNode node) {
return getChildren().indexOf(node);
}
@Override
default int getChildCount() {
return getChildren().size();
}
@Override
default TreeTableNode getChildAt(final int index) {
if (index < 0 || index >= getChildCount()) return null;
return getChildren().get(index);
}
@Override
default boolean isLeaf() {
return getChildCount() == 0;
}
List<Object> getColumns();
default Object getValueAt(final int column) {
return getColumns().get(column);
}
default Object getTreeValue() {
return getColumns().get(0);
}
default int getColumnCount() {
return getColumns().size();
}
default void setValueAt(final int column, final Object aValue) {
getColumns().set(column, aValue);
}
}

41
core/src/main/java/com/github/weisj/darklaf/ui/table/renderer/DarkTableCellRendererDelegate.java

@ -27,6 +27,7 @@ package com.github.weisj.darklaf.ui.table.renderer;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
@ -64,7 +65,7 @@ public class DarkTableCellRendererDelegate extends TableCellRendererDelegate imp
boolean paintSelected = isSelected && !isLeadSelectionCell && !table.isEditing(); boolean paintSelected = isSelected && !isLeadSelectionCell && !table.isEditing();
if (component instanceof JComponent) { if (component instanceof JComponent) {
setupBorderStyle(table, row, column, (JComponent) component, isRowFocus); setupBorderStyle(table, row, column, (JComponent) component, isLeadSelectionCell, isRowFocus);
} }
CellUtil.setupTableForeground(component, table, paintSelected); CellUtil.setupTableForeground(component, table, paintSelected);
CellUtil.setupTableBackground(component, table, paintSelected, row); CellUtil.setupTableBackground(component, table, paintSelected, row);
@ -72,26 +73,32 @@ public class DarkTableCellRendererDelegate extends TableCellRendererDelegate imp
} }
public void setupBorderStyle(final JTable table, final int row, final int column, public void setupBorderStyle(final JTable table, final int row, final int column,
final JComponent component, final boolean isRowFocus) { final JComponent component, final boolean isLeadSelectionCell,
if (isRowFocus final boolean isRowFocus) {
Border focusBorder = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
if (isLeadSelectionCell
&& table.getSelectionModel().getLeadSelectionIndex() == row && table.getSelectionModel().getLeadSelectionIndex() == row
&& DarkUIUtil.hasFocus(table) && DarkUIUtil.hasFocus(table)
&& !table.isEditing()) { && !table.isEditing()) {
LookAndFeel.installBorder(component, "Table.focusSelectedCellHighlightBorder"); PropertyUtil.installBorder(component, focusBorder);
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, true); if (isRowFocus) {
JTableHeader header = table.getTableHeader(); component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, true);
TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn(); JTableHeader header = table.getTableHeader();
boolean forceLeft = false; TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
boolean forceRight = false; boolean forceLeft = false;
if (draggedColumn != null) { boolean forceRight = false;
int index = DarkTableUI.viewIndexForColumn(draggedColumn, table); if (draggedColumn != null) {
forceLeft = column == index + 1 || column == index; int index = DarkTableUI.viewIndexForColumn(draggedColumn, table);
forceRight = column == index - 1 || column == index; forceLeft = column == index + 1 || column == index;
forceRight = column == index - 1 || column == index;
}
component.putClientProperty(KEY_FORCE_RIGHT_BORDER, forceRight);
component.putClientProperty(KEY_FORCE_LEFT_BORDER, forceLeft);
} else {
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, false);
} }
component.putClientProperty(KEY_FORCE_RIGHT_BORDER, forceRight); } else if (component.getBorder() == focusBorder) {
component.putClientProperty(KEY_FORCE_LEFT_BORDER, forceLeft); component.setBorder(null);
} else {
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, false);
} }
} }

399
core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java

@ -34,7 +34,10 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.*; import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import com.github.weisj.darklaf.graphics.PaintUtil; import com.github.weisj.darklaf.graphics.PaintUtil;
import com.github.weisj.darklaf.ui.cell.CellConstants; import com.github.weisj.darklaf.ui.cell.CellConstants;
@ -62,6 +65,9 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
public static final String STYLE_NONE = "none"; public static final String STYLE_NONE = "none";
public static final String KEY_IS_TREE_EDITOR = "JComponent.isTreeEditor"; public static final String KEY_IS_TREE_EDITOR = "JComponent.isTreeEditor";
public static final String KEY_IS_TREE_RENDERER = "JComponent.isTreeRenderer"; public static final String KEY_IS_TREE_RENDERER = "JComponent.isTreeRenderer";
public static final String KEY_IS_TABLE_TREE = "JComponent.isTableTree";
protected static final Rectangle boundsBuffer = new Rectangle();
protected MouseListener selectionListener; protected MouseListener selectionListener;
protected Color lineColor; protected Color lineColor;
@ -168,34 +174,33 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
tree.putClientProperty(KEY_MAC_ACTIONS_INSTALLED, Boolean.TRUE); tree.putClientProperty(KEY_MAC_ACTIONS_INSTALLED, Boolean.TRUE);
final InputMap inputMap = tree.getInputMap(JComponent.WHEN_FOCUSED); installInputMap(tree.getInputMap(JComponent.WHEN_FOCUSED));
inputMap.put(KeyStroke.getKeyStroke("pressed LEFT"), "collapse_or_move_up");
inputMap.put(KeyStroke.getKeyStroke("pressed RIGHT"), "expand_or_move_down");
inputMap.put(KeyStroke.getKeyStroke("pressed DOWN"), "move_down");
inputMap.put(KeyStroke.getKeyStroke("pressed UP"), "move_up");
inputMap.put(KeyStroke.getKeyStroke("pressed ENTER"), "toggle_edit");
final ActionMap actionMap = tree.getActionMap(); final ActionMap actionMap = tree.getActionMap();
final Action expandAction = actionMap.get("expand"); actionMap.put("expand_or_move_down", new TreeUIAction() {
if (expandAction != null) { @Override
actionMap.put("expand_or_move_down", new TreeUIAction() { public void actionPerformed(final ActionEvent e) {
@Override JTree tree = getTree(e);
public void actionPerformed(final ActionEvent e) { if (tree == null) return;
JTree tree = getTree(e); int selectionRow = tree.getLeadSelectionRow();
if (tree == null) return; if (selectionRow == -1) return;
int selectionRow = tree.getLeadSelectionRow();
if (selectionRow == -1) return; if (isLeaf(selectionRow) || tree.isExpanded(selectionRow)) {
if (!PropertyUtil.getBooleanProperty(tree, KEY_IS_TABLE_TREE)) {
if (isLeaf(selectionRow) || tree.isExpanded(selectionRow)) {
moveTo(tree, selectionRow + 1); moveTo(tree, selectionRow + 1);
} else {
expandAction.actionPerformed(e);
} }
tree.repaint(); } else {
tree.expandRow(selectionRow);
} }
}); tree.repaint();
} }
@Override
public boolean accept(final Object sender) {
return acceptExpandCollapseAction(sender, false);
}
});
actionMap.put("collapse_or_move_up", new TreeUIAction() { actionMap.put("collapse_or_move_up", new TreeUIAction() {
@Override @Override
@ -206,12 +211,19 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
if (selectionRow == -1) return; if (selectionRow == -1) return;
if (isLeaf(selectionRow) || tree.isCollapsed(selectionRow)) { if (isLeaf(selectionRow) || tree.isCollapsed(selectionRow)) {
moveTo(tree, selectionRow - 1); if (!PropertyUtil.getBooleanProperty(tree, KEY_IS_TABLE_TREE)) {
moveTo(tree, selectionRow - 1);
}
} else { } else {
tree.collapseRow(selectionRow); tree.collapseRow(selectionRow);
} }
tree.repaint(); tree.repaint();
} }
@Override
public boolean accept(final Object sender) {
return acceptExpandCollapseAction(sender, true);
}
}); });
actionMap.put("move_down", new TreeUIAction() { actionMap.put("move_down", new TreeUIAction() {
@ -236,6 +248,28 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
}); });
} }
protected boolean acceptExpandCollapseAction(final Object sender, final boolean collapsed) {
JTree tree = DarkUIUtil.nullableCast(JTree.class, sender);
if (tree == null) return false;
int selectionRow = tree.getLeadSelectionRow();
if (selectionRow == -1) return false;
final boolean collapsedOrExpanded = collapsed
? tree.isCollapsed(selectionRow)
: tree.isExpanded(selectionRow);
if (isLeaf(selectionRow) || collapsedOrExpanded) {
return !PropertyUtil.getBooleanProperty(tree, KEY_IS_TABLE_TREE);
}
return true;
}
protected void installInputMap(final InputMap inputMap) {
inputMap.put(KeyStroke.getKeyStroke("pressed LEFT"), "collapse_or_move_up");
inputMap.put(KeyStroke.getKeyStroke("pressed RIGHT"), "expand_or_move_down");
inputMap.put(KeyStroke.getKeyStroke("pressed DOWN"), "move_down");
inputMap.put(KeyStroke.getKeyStroke("pressed UP"), "move_up");
inputMap.put(KeyStroke.getKeyStroke("pressed ENTER"), "toggle_edit");
}
protected JTree getTree(final ActionEvent e) { protected JTree getTree(final ActionEvent e) {
return DarkUIUtil.nullableCast(JTree.class, e.getSource()); return DarkUIUtil.nullableCast(JTree.class, e.getSource());
} }
@ -268,29 +302,6 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
tree.scrollRectToVisible(bounds); tree.scrollRectToVisible(bounds);
} }
protected void collapse(final JTree tree) {
int selectionRow = tree.getLeadSelectionRow();
if (selectionRow == -1) return;
TreePath selectionPath = tree.getPathForRow(selectionRow);
if (selectionPath == null) return;
if (tree.getModel().isLeaf(selectionPath.getLastPathComponent())
|| tree.isCollapsed(selectionRow)) {
final TreePath parentPath = tree.getPathForRow(selectionRow).getParentPath();
if (parentPath != null) {
if (parentPath.getParentPath() != null || tree.isRootVisible()) {
final int parentRow = tree.getRowForPath(parentPath);
scrollRowToVisible(tree, parentRow);
tree.setSelectionRow(parentRow);
}
}
} else {
tree.collapseRow(selectionRow);
}
tree.repaint();
}
protected void toggleEdit(final JTree tree) { protected void toggleEdit(final JTree tree) {
if (tree == null) return; if (tree == null) return;
if (tree.isEditing()) { if (tree.isEditing()) {
@ -394,97 +405,14 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
Insets insets = tree.getInsets(); Insets insets = tree.getInsets();
TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y); TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
Enumeration<?> paintingEnumerator = treeState.getVisiblePathsFrom(initialPath); Enumeration<?> paintingEnumerator = treeState.getVisiblePathsFrom(initialPath);
int row = treeState.getRowForPath(initialPath);
int endY = paintBounds.y + paintBounds.height;
drawingCache.clear();
if (initialPath != null && paintingEnumerator != null) { if (initialPath != null && paintingEnumerator != null) {
TreePath parentPath = initialPath; int row = treeState.getRowForPath(initialPath);
// Draw the lines, knobs, and rows
boolean done = false; boolean done = false;
// Information for the node being rendered.
boolean isExpanded;
boolean hasBeenExpanded;
boolean isLeaf;
Rectangle boundsBuffer = new Rectangle();
Rectangle bounds;
TreePath path;
boolean rootVisible = isRootVisible();
final int containerWidth = tree.getParent() instanceof JViewport
? tree.getParent().getWidth()
: tree.getWidth();
final int xOffset = tree.getParent() instanceof JViewport
? ((JViewport) tree.getParent()).getViewPosition().x
: 0;
// Paint row backgrounds
Enumeration<?> backgroundEnumerator = treeState.getVisiblePathsFrom(initialPath);
while (backgroundEnumerator != null && backgroundEnumerator.hasMoreElements()) {
path = (TreePath) backgroundEnumerator.nextElement();
if (path != null) {
bounds = getPathBounds(path, insets, boundsBuffer);
if (bounds == null) return;
bounds.x = xOffset;
bounds.width = containerWidth;
if (paintBounds.intersects(bounds)) {
paintRowBackground(g, paintBounds, bounds, path, tree.getRowForPath(path));
}
}
}
// Find each parent and have them draw a line to their last child
while (parentPath != null) {
paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
drawingCache.put(parentPath, Boolean.TRUE);
parentPath = parentPath.getParentPath();
}
while (!done && paintingEnumerator.hasMoreElements()) { while (!done && paintingEnumerator.hasMoreElements()) {
path = (TreePath) paintingEnumerator.nextElement(); TreePath path = (TreePath) paintingEnumerator.nextElement();
if (path != null) { if (!paintSingleRow(g, paintBounds, insets, path, row)) {
isLeaf = treeModel.isLeaf(path.getLastPathComponent());
if (isLeaf) {
isExpanded = hasBeenExpanded = false;
} else {
isExpanded = treeState.getExpandedState(path);
hasBeenExpanded = tree.hasBeenExpanded(path);
}
bounds = getPathBounds(path, insets, boundsBuffer);
if (bounds == null)
// This will only happen if the model changes out
// from under us (usually in another thread).
// Swing isn't multithreaded, but I'll put this
// check in anyway.
{
return;
}
// See if the vertical line to the parent has been drawn.
parentPath = path.getParentPath();
if (parentPath != null) {
if (drawingCache.get(parentPath) == null) {
paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
drawingCache.put(parentPath, Boolean.TRUE);
}
paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded,
hasBeenExpanded, isLeaf);
} else if (rootVisible && row == 0) {
paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded,
hasBeenExpanded, isLeaf);
}
if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) {
paintExpandControl(g, paintBounds, insets, bounds, path, row, isExpanded,
hasBeenExpanded, isLeaf);
}
paintRow(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
if ((bounds.y + bounds.height) >= endY) {
done = true;
}
} else {
done = true; done = true;
} }
row++; row++;
@ -493,34 +421,54 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
paintDropLine(g); paintDropLine(g);
// Empty out the renderer pane, allowing renderers to be gc'ed. // Empty out the renderer pane, allowing renderers to be gc'ed.
rendererPane.removeAll(); rendererPane.removeAll();
drawingCache.clear();
} }
@Override public void paintRow(final Graphics g, final int row) {
public TreeCellRenderer getCellRenderer() { TreePath path = getPathForRow(tree, row);
return super.getCellRenderer(); Rectangle paintBounds = g.getClipBounds();
Insets insets = tree.getInsets();
Rectangle rowBounds = getPathBounds(path, insets, boundsBuffer);
g.translate(0, -rowBounds.y);
paintBounds.y += rowBounds.y;
paintSingleRow(g, paintBounds, insets, path, row);
rendererPane.removeAll();
g.translate(0, rowBounds.y);
} }
public Component getEditingComponent() { protected boolean paintSingleRow(final Graphics g, final Rectangle paintBounds, final Insets insets,
return editingComponent; final TreePath path, final int row) {
} if (path == null) return false;
final int xOffset = tree.getParent() instanceof JViewport
? ((JViewport) tree.getParent()).getViewPosition().x
: 0;
final int containerWidth = tree.getParent() instanceof JViewport
? tree.getParent().getWidth()
: tree.getWidth();
final Rectangle cellBounds = getPathBounds(path, insets, boundsBuffer);
if (cellBounds == null) return false;
final int boundsX = cellBounds.x;
final int boundsWidth = cellBounds.width;
public int getEditingRow() { cellBounds.x = xOffset;
return editingRow; cellBounds.width = containerWidth;
} paintRowBackground(g, paintBounds, cellBounds, path, row);
cellBounds.x = boundsX;
cellBounds.width = boundsWidth;
protected Rectangle getPathBounds(final TreePath path, final Insets insets, Rectangle bounds) { if (path.getParentPath() != null) {
bounds = treeState.getBounds(path, bounds); paintVerticalLegs(g, paintBounds, cellBounds, insets, path);
if (bounds != null) {
if (tree.getComponentOrientation().isLeftToRight()) {
bounds.x += insets.left;
} else {
bounds.x = tree.getWidth() - (bounds.x + bounds.width) -
insets.right;
}
bounds.y += insets.top;
} }
return bounds;
boolean isLeaf = treeModel.isLeaf(path.getLastPathComponent());
boolean isExpanded = !isLeaf && treeState.getExpandedState(path);
boolean hasBeenExpanded = !isLeaf && tree.hasBeenExpanded(path);
if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) {
paintExpandControl(g, paintBounds, insets, cellBounds, path, row, isExpanded,
hasBeenExpanded, isLeaf);
}
paintRow(g, paintBounds, insets, cellBounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
return (cellBounds.y + cellBounds.height) < paintBounds.y + paintBounds.height;
} }
protected void paintRowBackground(final Graphics g, final Rectangle clipBounds, protected void paintRowBackground(final Graphics g, final Rectangle clipBounds,
@ -541,12 +489,76 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
} }
} }
/*
* Paint all vertical legs for the whole tree in this row.
*/
protected void paintVerticalLegs(final Graphics g, final Rectangle clipBounds,
final Rectangle rowBounds,
final Insets insets, final TreePath path) {
if (!shouldPaintLines()) return;
int depth = path.getPathCount() - 1;
if (depth == 0 && (!isRootVisible() || !getShowsRootHandles())) {
// Parent is the root, which isn't visible.
return;
}
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
TreePath parentPath = path;
for (int currentDepth = depth - 1; currentDepth >= 0; currentDepth--) {
if (currentDepth == 0 && !isRootVisible()) continue;
int lineX = getRowX(-1, currentDepth);
if (tree.getComponentOrientation().isLeftToRight()) {
lineX = lineX - getRightChildIndent() + insets.left;
} else {
lineX = tree.getWidth() - lineX - insets.right + getRightChildIndent() - 1;
}
if (lineX > clipRight || lineX < clipLeft) continue;
parentPath = parentPath.getParentPath();
g.setColor(getLineColor(parentPath));
paintVerticalLine(g, tree, lineX, rowBounds.y, rowBounds.y + rowBounds.height);
}
}
@Override
public TreeCellRenderer getCellRenderer() {
return super.getCellRenderer();
}
public Component getEditingComponent() {
return editingComponent;
}
public int getEditingRow() {
return editingRow;
}
protected Rectangle getPathBounds(final TreePath path, final Insets insets, Rectangle bounds) {
bounds = treeState.getBounds(path, bounds);
if (bounds != null) {
if (tree.getComponentOrientation().isLeftToRight()) {
bounds.x += insets.left;
} else {
bounds.x = tree.getWidth() - (bounds.x + bounds.width) - insets.right;
}
bounds.y += insets.top;
}
return bounds;
}
@Override @Override
public void update(final Graphics g, final JComponent c) { public void update(final Graphics g, final JComponent c) {
if (popupListener != null) popupListener.repaint(); if (popupListener != null) popupListener.repaint();
super.update(g, c); super.update(g, c);
} }
public CellHintPopupListener<JTree, ?> getPopupListener() {
return popupListener;
}
@Override @Override
protected void updateRenderer() { protected void updateRenderer() {
super.updateRenderer(); super.updateRenderer();
@ -628,89 +640,14 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
@Override @Override
protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds,
final Insets insets, final TreePath path) { final Insets insets, final TreePath path) {}
if (!shouldPaintLines()) return;
int depth = path.getPathCount() - 1;
if (depth == 0 && !getShowsRootHandles() && !isRootVisible()) {
return;
}
if (treeModel.isLeaf(path.getLastPathComponent()) || !tree.isExpanded(path)) return;
int clipLeft = clipBounds.x;
int clipRight = clipBounds.x + (clipBounds.width - 1);
int clipTop = clipBounds.y;
int clipBottom = clipBounds.y + tree.getHeight();
Rectangle parentBounds = getPathBounds(tree, path);
int top;
int bottom;
int lineX = getRowX(-1, depth);
if (tree.getComponentOrientation().isLeftToRight()) {
lineX = lineX - getRightChildIndent() + insets.left;
} else {
lineX = tree.getWidth() - lineX - insets.right + getRightChildIndent() - 1;
}
if (lineX > clipRight || lineX < clipLeft) return;
if (parentBounds == null) {
top = Math.max(insets.top + getVerticalLegBuffer(), clipTop);
} else {
top = Math.max(parentBounds.y + parentBounds.height + getVerticalLegBuffer(), clipTop);
}
if (depth == 0 && !isRootVisible()) {
TreeModel model = getModel();
if (model != null) {
Object root = model.getRoot();
if (model.getChildCount(root) > 0) {
parentBounds = getPathBounds(tree, path.pathByAddingChild(model.getChild(root, 0)));
if (parentBounds != null) {
top = Math.max(insets.top + getVerticalLegBuffer(), parentBounds.y + parentBounds.height / 2);
}
}
}
}
int childCount = treeModel.getChildCount(path.getLastPathComponent());
g.setColor(getLineColor(path));
for (int i = 0; i < childCount - 1; i++) {
TreePath childPath = path.pathByAddingChild(treeModel.getChild(path.getLastPathComponent(), i));
Rectangle childBounds = getPathBounds(tree, childPath);
if (childBounds != null) {
bottom = Math.min(childBounds.y + childBounds.height, clipBottom);
paintVerticalLine(g, tree, lineX, top, bottom);
top = bottom;
if (clipBottom < top) return;
}
}
// Descend to deepest last child.
TreePath lastChildPath = path.pathByAddingChild(treeModel.getChild(path.getLastPathComponent(),
childCount - 1));
while (tree.isExpanded(lastChildPath)) {
int count = treeModel.getChildCount(lastChildPath.getLastPathComponent());
if (count == 0) break;
lastChildPath = lastChildPath.pathByAddingChild(treeModel.getChild(lastChildPath.getLastPathComponent(),
count - 1));
}
Rectangle childBounds = getPathBounds(tree, lastChildPath);
if (childBounds != null) {
bottom = Math.min(childBounds.y + childBounds.height, clipBottom);
paintVerticalLine(g, tree, lineX, top, bottom);
}
}
@Override @Override
protected void paintExpandControl(final Graphics g, final Rectangle clipBounds, final Insets insets, protected void paintExpandControl(final Graphics g, final Rectangle clipBounds, final Insets insets,
final Rectangle bounds, final TreePath path, final int row, final Rectangle bounds, final TreePath path, final int row,
final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf) { final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf) {
if (!isLeaf(row)) { if (!isLeaf(row)) {
boolean isPathSelected = tree.getSelectionModel().isPathSelected(path); boolean isPathSelected = tree.isPathSelected(path);
setExpandedIcon(getExpandedIcon(isPathSelected, tree.hasFocus() || tree.isEditing())); setExpandedIcon(getExpandedIcon(isPathSelected, tree.hasFocus() || tree.isEditing()));
setCollapsedIcon(getCollapsedIcon(isPathSelected, tree.hasFocus() || tree.isEditing())); setCollapsedIcon(getCollapsedIcon(isPathSelected, tree.hasFocus() || tree.isEditing()));
} }

17
core/src/test/java/ui/tree/TreeDemo.java

@ -54,12 +54,23 @@ public class TreeDemo implements ComponentDemo {
DefaultMutableTreeNode child1 = new SelectableTreeNode("Leaf B (boolean)", true); DefaultMutableTreeNode child1 = new SelectableTreeNode("Leaf B (boolean)", true);
DefaultMutableTreeNode parent2 = new DefaultMutableTreeNode("Node B"); DefaultMutableTreeNode parent2 = new DefaultMutableTreeNode("Node B");
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Leaf that is unnecessary verbose and ridiculously long C"); DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Leaf that is unnecessary verbose and ridiculously long C");
DefaultMutableTreeNode parent3 = new DefaultMutableTreeNode("Nested");
DefaultMutableTreeNode current = parent3;
for (int i = 0; i < 10; i++) {
DefaultMutableTreeNode node1 = new DefaultMutableTreeNode("Nested1 " + i);
DefaultMutableTreeNode node2 = new DefaultMutableTreeNode("Nested2 " + i);
current.add(node1);
current.add(node2);
current = node1;
}
parent1.add(child); parent1.add(child);
parent1.add(child1); parent1.add(child1);
parent2.add(child2); parent2.add(child2);
root.add(parent1); root.add(parent1);
root.add(parent2); root.add(parent2);
root.add(parent3);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
root.add(new DefaultMutableTreeNode("Leaf " + i)); root.add(new DefaultMutableTreeNode("Leaf " + i));
@ -103,6 +114,12 @@ public class TreeDemo implements ComponentDemo {
: ComponentOrientation.RIGHT_TO_LEFT)); : ComponentOrientation.RIGHT_TO_LEFT));
} }
}); });
controlPanel.add(new JCheckBox("show root") {
{
setSelected(tree.isRootVisible());
addActionListener(e -> tree.setRootVisible(isSelected()));
}
});
controlPanel.add(new JCheckBox("show root handles") { controlPanel.add(new JCheckBox("show root handles") {
{ {
setSelected(tree.getShowsRootHandles()); setSelected(tree.getShowsRootHandles());

96
core/src/test/java/ui/treetable/TreeTableDemo.java

@ -0,0 +1,96 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package ui.treetable;
import java.awt.*;
import java.util.Date;
import java.util.Random;
import javax.swing.*;
import com.github.weisj.darklaf.components.treetable.JTreeTable;
import com.github.weisj.darklaf.components.treetable.model.*;
import ui.ComponentDemo;
import ui.DemoPanel;
public class TreeTableDemo implements ComponentDemo {
public static void main(final String[] args) {
ComponentDemo.showDemo(new TreeTableDemo());
}
@Override
public JComponent createComponent() {
AbstractTreeTableModel treeTableModel = new DemoModel(createDataStructure());
JTreeTable treeTable = new JTreeTable(treeTableModel);
return new DemoPanel(new JScrollPane(treeTable), new BorderLayout(), 0);
}
private DemoNode createDataStructure() {
Random r = new Random();
DemoNode root = new DemoNode(null, "R1", "R1", new Date(r.nextLong()), 10);
for (int i = 0; i < 16; i++) {
DefaultTreeTableNode node = new DemoNode(root, "N" + i, "C1", new Date(r.nextLong()), 10);
if (i % 2 == 0) {
node.addChild(new DemoNode(node, "N12N12N12", "C12", new Date(r.nextLong()), 50));
node.addChild(new DemoNode(node, "N13N12N12", "C13", new Date(r.nextLong()), 60));
node.addChild(new DemoNode(node, "N14N12N12", "C14", new Date(r.nextLong()), 70));
node.addChild(new DemoNode(node, "N15N12N12", "C15", new Date(r.nextLong()), 80));
} else {
node.addChild(new DemoNode(node, "N12N12N12", "C12", new Date(r.nextLong()), 10));
node.addChild(new DemoNode(node, "N13N12N12", "C13", new Date(r.nextLong()), 20));
node.addChild(new DemoNode(node, "N14N12N12", "C14", new Date(r.nextLong()), 30));
node.addChild(new DemoNode(node, "N15N12N12", "C15", new Date(r.nextLong()), 40));
}
root.addChild(node);
}
return root;
}
@Override
public String getTitle() {
return "TreeTable Demo";
}
protected static class DemoNode extends DefaultTreeTableNode {
public DemoNode(final TreeTableNode parent, final String name, final String capital,
final Date declared, final Integer area) {
super(parent, new Object[]{name, capital, declared, area});
}
}
protected static class DemoModel extends DefaultTreeTableModel {
static protected String[] columnNames = {"TreeNode", "String", "Date", "Integer"};
static protected Class<?>[] columnTypes = {TreeTableModel.class, String.class, Date.class, Integer.class};
public DemoModel(final TreeTableNode rootNode) {
super(rootNode, columnNames);
setColumnClasses(columnTypes);
root = rootNode;
}
}
}

9
utils/src/main/java/com/github/weisj/darklaf/util/PropertyUtil.java

@ -31,6 +31,7 @@ import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
public class PropertyUtil { public class PropertyUtil {
@ -51,6 +52,14 @@ public class PropertyUtil {
} }
} }
public static void installBorder(final JComponent component, final Border border) {
if (component == null) return;
Border b = component.getBorder();
if (b == null || b instanceof UIResource) {
component.setBorder(border);
}
}
public static void installProperty(final JComponent c, final String key, final Object value) { public static void installProperty(final JComponent c, final String key, final Object value) {
if (c.getClientProperty(key) == null) { if (c.getClientProperty(key) == null) {
c.putClientProperty(key, value); c.putClientProperty(key, value);

Loading…
Cancel
Save