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 javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
@ -64,7 +65,7 @@ public class DarkTableCellRendererDelegate extends TableCellRendererDelegate imp
boolean paintSelected = isSelected && !isLeadSelectionCell && !table.isEditing();
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.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,
final JComponent component, final boolean isRowFocus) {
if (isRowFocus
final JComponent component, final boolean isLeadSelectionCell,
final boolean isRowFocus) {
Border focusBorder = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
if (isLeadSelectionCell
&& table.getSelectionModel().getLeadSelectionIndex() == row
&& DarkUIUtil.hasFocus(table)
&& !table.isEditing()) {
LookAndFeel.installBorder(component, "Table.focusSelectedCellHighlightBorder");
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, true);
JTableHeader header = table.getTableHeader();
TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
boolean forceLeft = false;
boolean forceRight = false;
if (draggedColumn != null) {
int index = DarkTableUI.viewIndexForColumn(draggedColumn, table);
forceLeft = column == index + 1 || column == index;
forceRight = column == index - 1 || column == index;
PropertyUtil.installBorder(component, focusBorder);
if (isRowFocus) {
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, true);
JTableHeader header = table.getTableHeader();
TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
boolean forceLeft = false;
boolean forceRight = false;
if (draggedColumn != null) {
int index = DarkTableUI.viewIndexForColumn(draggedColumn, table);
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);
component.putClientProperty(KEY_FORCE_LEFT_BORDER, forceLeft);
} else {
component.putClientProperty(KEY_FULL_ROW_FOCUS_BORDER, false);
} else if (component.getBorder() == focusBorder) {
component.setBorder(null);
}
}

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.UIResource;
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.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 KEY_IS_TREE_EDITOR = "JComponent.isTreeEditor";
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 Color lineColor;
@ -168,34 +174,33 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
tree.putClientProperty(KEY_MAC_ACTIONS_INSTALLED, Boolean.TRUE);
final InputMap inputMap = 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");
installInputMap(tree.getInputMap(JComponent.WHEN_FOCUSED));
final ActionMap actionMap = tree.getActionMap();
final Action expandAction = actionMap.get("expand");
if (expandAction != null) {
actionMap.put("expand_or_move_down", new TreeUIAction() {
@Override
public void actionPerformed(final ActionEvent e) {
JTree tree = getTree(e);
if (tree == null) return;
int selectionRow = tree.getLeadSelectionRow();
if (selectionRow == -1) return;
if (isLeaf(selectionRow) || tree.isExpanded(selectionRow)) {
actionMap.put("expand_or_move_down", new TreeUIAction() {
@Override
public void actionPerformed(final ActionEvent e) {
JTree tree = getTree(e);
if (tree == null) return;
int selectionRow = tree.getLeadSelectionRow();
if (selectionRow == -1) return;
if (isLeaf(selectionRow) || tree.isExpanded(selectionRow)) {
if (!PropertyUtil.getBooleanProperty(tree, KEY_IS_TABLE_TREE)) {
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() {
@Override
@ -206,12 +211,19 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
if (selectionRow == -1) return;
if (isLeaf(selectionRow) || tree.isCollapsed(selectionRow)) {
moveTo(tree, selectionRow - 1);
if (!PropertyUtil.getBooleanProperty(tree, KEY_IS_TABLE_TREE)) {
moveTo(tree, selectionRow - 1);
}
} else {
tree.collapseRow(selectionRow);
}
tree.repaint();
}
@Override
public boolean accept(final Object sender) {
return acceptExpandCollapseAction(sender, true);
}
});
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) {
return DarkUIUtil.nullableCast(JTree.class, e.getSource());
}
@ -268,29 +302,6 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
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) {
if (tree == null) return;
if (tree.isEditing()) {
@ -394,97 +405,14 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
Insets insets = tree.getInsets();
TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
Enumeration<?> paintingEnumerator = treeState.getVisiblePathsFrom(initialPath);
int row = treeState.getRowForPath(initialPath);
int endY = paintBounds.y + paintBounds.height;
drawingCache.clear();
if (initialPath != null && paintingEnumerator != null) {
TreePath parentPath = initialPath;
// Draw the lines, knobs, and rows
int row = treeState.getRowForPath(initialPath);
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()) {
path = (TreePath) paintingEnumerator.nextElement();
if (path != null) {
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 {
TreePath path = (TreePath) paintingEnumerator.nextElement();
if (!paintSingleRow(g, paintBounds, insets, path, row)) {
done = true;
}
row++;
@ -493,34 +421,54 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
paintDropLine(g);
// Empty out the renderer pane, allowing renderers to be gc'ed.
rendererPane.removeAll();
drawingCache.clear();
}
@Override
public TreeCellRenderer getCellRenderer() {
return super.getCellRenderer();
public void paintRow(final Graphics g, final int row) {
TreePath path = getPathForRow(tree, row);
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() {
return editingComponent;
}
protected boolean paintSingleRow(final Graphics g, final Rectangle paintBounds, final Insets insets,
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() {
return editingRow;
}
cellBounds.x = xOffset;
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) {
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;
if (path.getParentPath() != null) {
paintVerticalLegs(g, paintBounds, cellBounds, insets, path);
}
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,
@ -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
public void update(final Graphics g, final JComponent c) {
if (popupListener != null) popupListener.repaint();
super.update(g, c);
}
public CellHintPopupListener<JTree, ?> getPopupListener() {
return popupListener;
}
@Override
protected void updateRenderer() {
super.updateRenderer();
@ -628,89 +640,14 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
@Override
protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds,
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);
}
}
final Insets insets, final TreePath path) {}
@Override
protected void paintExpandControl(final Graphics g, final Rectangle clipBounds, final Insets insets,
final Rectangle bounds, final TreePath path, final int row,
final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf) {
if (!isLeaf(row)) {
boolean isPathSelected = tree.getSelectionModel().isPathSelected(path);
boolean isPathSelected = tree.isPathSelected(path);
setExpandedIcon(getExpandedIcon(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 parent2 = new DefaultMutableTreeNode("Node B");
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(child1);
parent2.add(child2);
root.add(parent1);
root.add(parent2);
root.add(parent3);
for (int i = 0; i < 100; i++) {
root.add(new DefaultMutableTreeNode("Leaf " + i));
@ -103,6 +114,12 @@ public class TreeDemo implements ComponentDemo {
: 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") {
{
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 javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
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) {
if (c.getClientProperty(key) == null) {
c.putClientProperty(key, value);

Loading…
Cancel
Save