Compare commits

...

2 Commits

Author SHA1 Message Date
Bane Stojkovic 525024ac56 Merge branch 'master' into explorer-tree 2026-01-07 19:39:38 +01:00
Bane Stojkovic 1a31819431 explorer tree 2025-12-29 12:30:44 +01:00
4 changed files with 679 additions and 207 deletions
@@ -0,0 +1,96 @@
package org.diylc.swing.plugins.explorer;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.Icon;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.diylc.common.ComponentType;
import org.diylc.core.IDIYComponent;
public class ComponentTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
// private static Color[] layerColors = new Color[] { Color.gray, Color.decode("#ff6363"),
// Color.decode("#293462"), Color.decode("#138588"),
// Color.decode("#c0ca03"), Color.decode("#990000") };
private Map<String, ComponentType> componentTypes;
public ComponentTreeCellRenderer(Map<String, List<ComponentType>> componentTypeMap) {
this.componentTypes = componentTypeMap.values().stream().flatMap(x -> x.stream())
.collect(Collectors.toMap(x -> x.getInstanceClass().getCanonicalName(), x -> x));
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
if (!(value instanceof DefaultMutableTreeNode)) {
return this;
}
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
// Handle group nodes
if (userObject instanceof ComponentTreeModel.GroupNode) {
ComponentTreeModel.GroupNode groupNode = (ComponentTreeModel.GroupNode) userObject;
setText(groupNode.toString());
setIcon(null); // No icon for group nodes
setPreferredSize(new Dimension(250, 16));
return this;
}
if (!(userObject instanceof IDIYComponent)) {
return this;
}
IDIYComponent<?> component = (IDIYComponent<?>) userObject;
ComponentType componentType = componentTypes.get(component.getClass().getCanonicalName());
if (componentType == null) {
return this;
}
// Set the component's icon
Icon icon = componentType.getIcon();
if (icon != null) {
setIcon(icon);
}
int layerId = (int) Math.round(componentType.getZOrder());
String valueForDisplay = component.getValueForDisplay();
if (selected) {
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
setText(String.format("[L%s] %s", layerId, component.getName()));
} else {
setText(String.format("[L%s] %s (%s)", layerId, component.getName(), valueForDisplay));
}
} else {
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
layerId, component.getName()));
} else {
setText(String.format(
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
layerId, component.getName(), valueForDisplay));
}
}
// Set preferred size after all properties are set (like TreePanel does)
setPreferredSize(new Dimension(250, 16));
return this;
}
}
@@ -0,0 +1,266 @@
package org.diylc.swing.plugins.explorer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import org.diylc.core.IDIYComponent;
public class ComponentTreeModel extends DefaultTreeModel {
private static final long serialVersionUID = 1L;
private DefaultMutableTreeNode root;
private List<IDIYComponent<?>> componentsRaw;
private List<IDIYComponent<?>> components;
private Set<Set<IDIYComponent<?>>> groups;
private String searchText;
private ExplorerPane.Sort sort = ExplorerPane.Sort.Z_INDEX;
public ComponentTreeModel() {
super(new DefaultMutableTreeNode("Root"));
this.root = (DefaultMutableTreeNode) getRoot();
}
public void setComponents(List<IDIYComponent<?>> components, Set<Set<IDIYComponent<?>>> groups) {
this.componentsRaw = components;
this.groups = groups != null ? groups : new HashSet<>();
refresh();
}
public void setComponents(List<IDIYComponent<?>> components) {
setComponents(components, null);
}
/**
* Returns the group that contains the given component, or null if not in a group.
*/
public Set<IDIYComponent<?>> getGroupForComponent(IDIYComponent<?> component) {
if (groups == null) {
return null;
}
for (Set<IDIYComponent<?>> group : groups) {
if (group.contains(component)) {
return group;
}
}
return null;
}
public List<IDIYComponent<?>> getComponents() {
return components;
}
public ExplorerPane.Sort getSort() {
return sort;
}
public void setSort(ExplorerPane.Sort sort) {
this.sort = sort;
refresh();
}
public void setSearchText(String searchText) {
this.searchText = searchText.toLowerCase();
refresh();
}
private void refresh() {
// Clear existing children
root.removeAllChildren();
// Sort components
if (sort == ExplorerPane.Sort.NAME) {
this.components =
componentsRaw.stream().sorted(Comparator.comparing(x -> x.toString().toLowerCase()))
.collect(Collectors.toList());
} else if (sort == ExplorerPane.Sort.TYPE) {
this.components = componentsRaw.stream()
.sorted(Comparator.comparing(x -> x.getClass().getName().toLowerCase())
.thenComparing(x -> x.toString().toLowerCase()))
.collect(Collectors.toList());
} else {
List<IDIYComponent<?>> componentsClone = new ArrayList<IDIYComponent<?>>(componentsRaw);
Collections.reverse(componentsClone);
this.components = componentsClone;
}
// Filter by search text
if (searchText != null && !searchText.trim().isEmpty()) {
this.components = this.components.stream().filter(component -> {
return component.getName().toLowerCase().contains(searchText);
}).collect(Collectors.toList());
}
// Build a map of components to their groups
Set<IDIYComponent<?>> groupedComponents = new HashSet<>();
if (groups != null) {
for (Set<IDIYComponent<?>> group : groups) {
groupedComponents.addAll(group);
}
}
// Create a list to hold all tree items (groups and ungrouped components) for sorting
List<TreeItem> allItems = new ArrayList<>();
// Create group nodes
if (groups != null && !groups.isEmpty()) {
for (Set<IDIYComponent<?>> group : groups) {
// Get ALL group components from raw list (for maxIndex calculation)
List<IDIYComponent<?>> groupComponentsRaw = componentsRaw.stream()
.filter(group::contains)
.collect(Collectors.toList());
// Apply search filter to get components to display
List<IDIYComponent<?>> groupComponents = groupComponentsRaw.stream()
.filter(component -> {
if (searchText != null && !searchText.trim().isEmpty()) {
return component.getName().toLowerCase().contains(searchText);
}
return true;
})
.collect(Collectors.toList());
if (!groupComponents.isEmpty()) {
// Sort components within the group based on sort mode
if (sort == ExplorerPane.Sort.NAME) {
groupComponents.sort(Comparator.comparing(x -> x.toString().toLowerCase()));
} else if (sort == ExplorerPane.Sort.TYPE) {
groupComponents.sort(Comparator.comparing(x -> x.getClass().getName().toLowerCase())
.thenComparing(x -> x.toString().toLowerCase()));
} else {
// For Z_INDEX, sort by index in raw list (reverse order - highest first)
groupComponents.sort((c1, c2) -> {
int idx1 = componentsRaw.indexOf(c1);
int idx2 = componentsRaw.indexOf(c2);
return Integer.compare(idx2, idx1); // Reverse: higher index first
});
}
// Store both the display components and all group components (for maxIndex calculation)
allItems.add(new TreeItem(group, groupComponents, groupComponentsRaw, true));
}
}
}
// Add ungrouped components
for (IDIYComponent<?> component : this.components) {
if (!groupedComponents.contains(component)) {
allItems.add(new TreeItem(null, Collections.singletonList(component), Collections.singletonList(component), false));
}
}
// Sort all items together based on sort mode
if (sort == ExplorerPane.Sort.Z_INDEX) {
// Sort by maximum z-order (index) - groups use max index of ALL their components (not just filtered)
Collections.sort(allItems, (item1, item2) -> {
int maxIndex1 = getMaxComponentIndex(item1.allComponents);
int maxIndex2 = getMaxComponentIndex(item2.allComponents);
// Reverse order: higher index first (since components are in reverse order)
return Integer.compare(maxIndex2, maxIndex1);
});
} else if (sort == ExplorerPane.Sort.NAME) {
Collections.sort(allItems, (item1, item2) -> {
String name1 = item1.components.get(0).getName().toLowerCase();
String name2 = item2.components.get(0).getName().toLowerCase();
return name1.compareTo(name2);
});
} else if (sort == ExplorerPane.Sort.TYPE) {
Collections.sort(allItems, (item1, item2) -> {
String type1 = item1.components.get(0).getClass().getName().toLowerCase();
String type2 = item2.components.get(0).getClass().getName().toLowerCase();
int typeCompare = type1.compareTo(type2);
if (typeCompare != 0) {
return typeCompare;
}
String name1 = item1.components.get(0).getName().toLowerCase();
String name2 = item2.components.get(0).getName().toLowerCase();
return name1.compareTo(name2);
});
}
// Add sorted items to the tree
for (TreeItem item : allItems) {
if (item.isGroup) {
DefaultMutableTreeNode groupNode = new DefaultMutableTreeNode(new GroupNode(item.group));
for (IDIYComponent<?> component : item.components) {
DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(component);
groupNode.add(componentNode);
}
root.add(groupNode);
} else {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(item.components.get(0));
root.add(node);
}
}
// Notify tree model listeners
nodeStructureChanged(root);
}
/**
* Wrapper class to represent a group node in the tree.
*/
public static class GroupNode {
private final Set<IDIYComponent<?>> group;
public GroupNode(Set<IDIYComponent<?>> group) {
this.group = group;
}
public Set<IDIYComponent<?>> getGroup() {
return group;
}
@Override
public String toString() {
return "Group (" + group.size() + " components)";
}
}
public int getSize() {
return this.components == null ? 0 : this.components.size();
}
/**
* Gets the maximum index of components in the raw components list.
* This represents the maximum z-order for the group.
* Returns -1 if no valid index is found.
*/
private int getMaxComponentIndex(List<IDIYComponent<?>> groupComponents) {
int maxIndex = -1;
for (IDIYComponent<?> component : groupComponents) {
int index = componentsRaw.indexOf(component);
if (index >= 0 && index > maxIndex) {
maxIndex = index;
}
}
return maxIndex;
}
/**
* Helper class to hold either a group or a single ungrouped component for sorting.
*/
private static class TreeItem {
final Set<IDIYComponent<?>> group;
final List<IDIYComponent<?>> components; // Components to display (may be filtered)
final List<IDIYComponent<?>> allComponents; // All components for maxIndex calculation
final boolean isGroup;
TreeItem(Set<IDIYComponent<?>> group, List<IDIYComponent<?>> components,
List<IDIYComponent<?>> allComponents, boolean isGroup) {
this.group = group;
this.components = components;
this.allComponents = allComponents;
this.isGroup = isGroup;
}
}
}
@@ -0,0 +1,213 @@
package org.diylc.swing.plugins.explorer;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.util.List;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IDIYComponent;
import org.diylc.lang.LangUtil;
import org.diylc.swing.ISwingUI;
public class ComponentTreeTransferHandler extends TransferHandler {
private static final long serialVersionUID = 1L;
private static final String REORDERING_NOT_POSSIBLE = LangUtil.translate(
"Component reordering is possible only when Project Explorer is sorted by z-index.");
private final ComponentTreeModel treeModel;
private final ISwingUI swingUI;
private final IPlugInPort plugInPort;
public ComponentTreeTransferHandler(ComponentTreeModel treeModel, ISwingUI swingUI,
IPlugInPort plugInPort) {
this.treeModel = treeModel;
this.swingUI = swingUI;
this.plugInPort = plugInPort;
}
@Override
public int getSourceActions(JComponent c) {
return MOVE;
}
@Override
protected Transferable createTransferable(JComponent c) {
if (!(c instanceof JTree)) {
return null;
}
JTree tree = (JTree) c;
TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths == null || selectionPaths.length == 0) {
return null;
}
// Get the first selected path
TreePath firstPath = selectionPaths[0];
DefaultMutableTreeNode node = (DefaultMutableTreeNode) firstPath.getLastPathComponent();
Object userObject = node.getUserObject();
// Only allow dragging components, not group nodes
if (!(userObject instanceof IDIYComponent)) {
return null;
}
// Store the component's group info in the transferable
IDIYComponent<?> component = (IDIYComponent<?>) userObject;
Set<IDIYComponent<?>> group = treeModel.getGroupForComponent(component);
String groupId = group != null ? Integer.toString(group.hashCode()) : "null";
int row = tree.getRowForPath(firstPath);
return new StringSelection(row + ":" + groupId);
}
@Override
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
if (!(support.getComponent() instanceof JTree)) {
return false;
}
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
TreePath dropPath = dl.getPath();
if (dropPath == null) {
return false;
}
// Only allow reordering when sorted by Z_INDEX
if (treeModel.getSort() != ExplorerPane.Sort.Z_INDEX) {
return false;
}
// Check if dropping on a group node - not allowed
DefaultMutableTreeNode dropNode = (DefaultMutableTreeNode) dropPath.getLastPathComponent();
if (dropNode.getUserObject() instanceof ComponentTreeModel.GroupNode) {
return false;
}
// Get the source component's group from the transferable
try {
Transferable transferable = support.getTransferable();
String data = (String) transferable.getTransferData(DataFlavor.stringFlavor);
String[] parts = data.split(":");
if (parts.length == 2) {
String sourceGroupId = parts[1];
// Get the target component's group
Object targetUserObject = dropNode.getUserObject();
if (targetUserObject instanceof IDIYComponent) {
IDIYComponent<?> targetComponent = (IDIYComponent<?>) targetUserObject;
Set<IDIYComponent<?>> targetGroup = treeModel.getGroupForComponent(targetComponent);
String targetGroupId = targetGroup != null ? Integer.toString(targetGroup.hashCode()) : "null";
// Only allow dragging within the same group (or both ungrouped)
if (!sourceGroupId.equals(targetGroupId)) {
return false;
}
}
}
} catch (Exception e) {
// If we can't parse the transferable, don't allow the drop
return false;
}
return true;
}
@Override
public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
// Show error message if not sorted by Z_INDEX
if (treeModel.getSort() != ExplorerPane.Sort.Z_INDEX) {
swingUI.showMessage(REORDERING_NOT_POSSIBLE, "Error", ISwingUI.ERROR_MESSAGE);
}
return false;
}
if (!(support.getComponent() instanceof JTree)) {
return false;
}
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
TreePath dropPath = dl.getPath();
if (dropPath == null) {
return false;
}
DefaultMutableTreeNode dropNode = (DefaultMutableTreeNode) dropPath.getLastPathComponent();
Object dropUserObject = dropNode.getUserObject();
// Can't drop on group nodes
if (dropUserObject instanceof ComponentTreeModel.GroupNode) {
return false;
}
if (!(dropUserObject instanceof IDIYComponent)) {
return false;
}
// Get the parent - if it's a group node, we need to calculate index within the group
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dropNode.getParent();
if (parent != null && parent.getUserObject() instanceof ComponentTreeModel.GroupNode) {
// Dropping within a group - calculate index within the group's children
int childIndex = dl.getChildIndex();
int dropTargetIndex;
if (childIndex != -1) {
dropTargetIndex = childIndex;
} else {
// Dropping on a component within the group
dropTargetIndex = parent.getIndex(dropNode);
}
// For groups, we need to calculate z-index based on the group's components
// Get all components in the group in z-order (they're already sorted in the model)
ComponentTreeModel.GroupNode groupNode = (ComponentTreeModel.GroupNode) parent.getUserObject();
List<IDIYComponent<?>> groupComponents = treeModel.getComponents().stream()
.filter(groupNode.getGroup()::contains)
.collect(java.util.stream.Collectors.toList());
if (dropTargetIndex < groupComponents.size()) {
IDIYComponent<?> targetComponent = groupComponents.get(dropTargetIndex);
// Find the component's index in the full component list
int componentIndex = treeModel.getComponents().indexOf(targetComponent);
if (componentIndex >= 0) {
int targetZIndex = treeModel.getSize() - componentIndex;
plugInPort.moveSelectionToZIndex(targetZIndex);
}
}
} else {
// Dropping on an ungrouped component at root level
DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();
int childIndex = dl.getChildIndex();
int dropTargetIndex;
if (childIndex != -1) {
dropTargetIndex = childIndex;
} else {
dropTargetIndex = root.getIndex(dropNode);
}
// Calculate the target z-index (components are in reverse order for z-index)
int targetZIndex = treeModel.getSize() - dropTargetIndex;
plugInPort.moveSelectionToZIndex(targetZIndex);
}
return true;
}
}
@@ -1,6 +1,5 @@
package org.diylc.swing.plugins.explorer;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
@@ -10,47 +9,36 @@ import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.AbstractListModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DropMode;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import org.diylc.common.ComponentType;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IDIYComponent;
import org.diylc.lang.LangUtil;
import org.diylc.swing.ISwingUI;
import org.diylc.swing.gui.ComponentPopupMenu;
import org.diylc.swing.gui.DragDropList;
import org.diylc.swing.gui.IDragDropListListener;
import org.diylc.swing.gui.SearchTextField;
public class ExplorerPane extends JPanel {
private static final long serialVersionUID = 1L;
private static final String REORDERING_NOT_POSSIBLE = LangUtil.translate(
"Component reordering is possible only when Project Explorer is sorted by z-index.");
public static Font DEFAULT_FONT = new Font("Square721 BT", Font.PLAIN, 12);
private ISwingUI swingUI;
@@ -59,8 +47,8 @@ public class ExplorerPane extends JPanel {
private JLabel titleLabel;
private JTextField searchField;
private JScrollPane componentScrollPane;
private ComponentListModel componentListModel;
private JList<IDIYComponent<?>> componentList;
private ComponentTreeModel componentTreeModel;
private JTree componentTree;
private ComponentPopupMenu popupMenu;
private JComboBox<Sort> sortBox;
@@ -116,7 +104,7 @@ public class ExplorerPane extends JPanel {
public JTextField getSearchField() {
if (searchField == null) {
searchField = new SearchTextField(true, 'X', text -> {
getComponentListModel().setSearchText(text);
getComponentTreeModel().setSearchText(text);
});
}
return searchField;
@@ -129,7 +117,7 @@ public class ExplorerPane extends JPanel {
@Override
public void itemStateChanged(ItemEvent e) {
getComponentListModel().setSort((Sort) e.getItem());
getComponentTreeModel().setSort((Sort) e.getItem());
}
});
}
@@ -138,38 +126,37 @@ public class ExplorerPane extends JPanel {
public JScrollPane getComponentScrollPane() {
if (componentScrollPane == null) {
componentScrollPane = new JScrollPane(getComponentList(),
componentScrollPane = new JScrollPane(getComponentTree(),
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
return componentScrollPane;
}
public JList<IDIYComponent<?>> getComponentList() {
if (componentList == null) {
componentList = new DragDropList<IDIYComponent<?>>(new IDragDropListListener() {
public JTree getComponentTree() {
if (componentTree == null) {
componentTree = new JTree(getComponentTreeModel());
componentTree.setRootVisible(false);
componentTree.setShowsRootHandles(true);
componentTree.setFont(DEFAULT_FONT);
componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
componentTree.setCellRenderer(new ComponentTreeCellRenderer(plugInPort.getComponentTypes()));
// Set row height after renderer is set, using a fixed value
componentTree.setRowHeight(24);
// Enable drag and drop
componentTree.setDragEnabled(true);
componentTree.setDropMode(DropMode.INSERT);
componentTree.setTransferHandler(new ComponentTreeTransferHandler(getComponentTreeModel(),
swingUI, plugInPort));
componentTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public boolean dropComplete(int selectedIndex, int dropTargetIndex) {
if (getComponentListModel().getSort() != Sort.Z_INDEX) {
swingUI.showMessage(REORDERING_NOT_POSSIBLE, "Error", ISwingUI.ERROR_MESSAGE);
return false;
}
plugInPort.moveSelectionToZIndex(getComponentListModel().getSize() - dropTargetIndex);
return true;
}
});
componentList.setModel(getComponentListModel());
componentList.setFont(DEFAULT_FONT);
componentList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
componentList.setCellRenderer(new ComponentListCellRenderer(plugInPort.getComponentTypes()));
componentList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting())
public void valueChanged(TreeSelectionEvent e) {
if (e.getNewLeadSelectionPath() == null)
return;
List<IDIYComponent<?>> selectedValuesList = componentList.getSelectedValuesList();
List<IDIYComponent<?>> selectedValuesList = getSelectedComponents();
if (selectedValuesList.size() > 0 && !new HashSet<IDIYComponent<?>>(selectedValuesList)
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents()))) {
plugInPort.setSelection(selectedValuesList, true);
@@ -177,7 +164,7 @@ public class ExplorerPane extends JPanel {
}
});
componentList.addMouseListener(new MouseAdapter() {
componentTree.addMouseListener(new MouseAdapter() {
private MouseEvent pressedEvent;
@@ -190,7 +177,7 @@ public class ExplorerPane extends JPanel {
@Override
public void mousePressed(MouseEvent e) {
componentList.requestFocus();
componentTree.requestFocus();
pressedEvent = e;
}
@@ -205,7 +192,7 @@ public class ExplorerPane extends JPanel {
if (plugInPort.getNewComponentTypeSlot() == null && (e.isPopupTrigger()
|| (pressedEvent != null && pressedEvent.isPopupTrigger()))) {
final List<IDIYComponent<?>> selectedValues = componentList.getSelectedValuesList();
final List<IDIYComponent<?>> selectedValues = getSelectedComponents();
getComponentPopupMenu().prepareAndShowAt(selectedValues, selectedValues, e.getX(),
e.getY());
}
@@ -214,193 +201,103 @@ public class ExplorerPane extends JPanel {
}
});
}
return componentList;
return componentTree;
}
private List<IDIYComponent<?>> getSelectedComponents() {
List<IDIYComponent<?>> selectedComponents = new ArrayList<>();
TreePath[] selectionPaths = componentTree.getSelectionPaths();
if (selectionPaths != null) {
for (TreePath path : selectionPaths) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) lastPathComponent;
Object userObject = node.getUserObject();
if (userObject instanceof IDIYComponent) {
selectedComponents.add((IDIYComponent<?>) userObject);
}
}
}
}
return selectedComponents;
}
public ComponentListModel getComponentListModel() {
if (componentListModel == null) {
componentListModel = new ComponentListModel();
public ComponentTreeModel getComponentTreeModel() {
if (componentTreeModel == null) {
componentTreeModel = new ComponentTreeModel();
}
return componentListModel;
return componentTreeModel;
}
public ComponentPopupMenu getComponentPopupMenu() {
if (popupMenu == null) {
popupMenu = new ComponentPopupMenu(plugInPort, getComponentList(), false);
popupMenu = new ComponentPopupMenu(plugInPort, getComponentTree(), false);
}
return popupMenu;
}
public void setComponents(List<IDIYComponent<?>> components,
Collection<IDIYComponent<?>> selectedComponents) {
this.getComponentListModel().setComponents(components);
// Get groups from the current project
Set<Set<IDIYComponent<?>>> groups = null;
if (plugInPort.getCurrentProject() != null) {
groups = plugInPort.getCurrentProject().getGroups();
}
this.getComponentTreeModel().setComponents(components, groups);
setSelection(new HashSet<IDIYComponent<?>>(selectedComponents));
// Expand all group nodes after model is updated
SwingUtilities.invokeLater(() -> {
JTree tree = getComponentTree();
for (int i = 0; i < tree.getRowCount(); i++) {
TreePath path = tree.getPathForRow(i);
if (path != null) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
if (node.getUserObject() instanceof ComponentTreeModel.GroupNode) {
tree.expandPath(path);
}
}
}
});
}
public void setSelection(Set<IDIYComponent<?>> selectedComponents) {
List<IDIYComponent<?>> selectedValuesList = componentList.getSelectedValuesList();
if (new HashSet<IDIYComponent<?>>(selectedValuesList)
List<IDIYComponent<?>> currentSelected = getSelectedComponents();
if (new HashSet<IDIYComponent<?>>(currentSelected)
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents())))
return;
final JList<IDIYComponent<?>> list = this.getComponentList();
final JTree tree = this.getComponentTree();
if (selectedComponents.size() == 0) {
list.clearSelection();
tree.clearSelection();
} else {
int[] indices = new int[selectedComponents.size()];
int n = 0;
List<IDIYComponent<?>> components = this.getComponentListModel().getComponents();
for (int i = 0; i < components.size(); i++) {
indices[n] = -1; // this should not happen
if (selectedComponents.contains(components.get(i)))
indices[n++] = i;
if (n == indices.length)
break;
}
list.getSelectionModel().setValueIsAdjusting(true);
list.setSelectedIndices(indices);
list.getSelectionModel().setValueIsAdjusting(false);
int firstVisibleIndex = list.getFirstVisibleIndex();
int lastVisibleIndex = list.getFirstVisibleIndex();
// if none of the selected items is visible, jump to the first one
if (!Arrays.stream(indices)
.anyMatch(index -> firstVisibleIndex <= index && index <= lastVisibleIndex))
list.ensureIndexIsVisible(indices[0]);
}
}
private class ComponentListModel extends AbstractListModel<IDIYComponent<?>> {
private static final long serialVersionUID = 1L;
private List<IDIYComponent<?>> componentsRaw;
private List<IDIYComponent<?>> components;
private String searchText;
private Sort sort = Sort.Z_INDEX;
public void setComponents(List<IDIYComponent<?>> components) {
this.componentsRaw = components;
int oldSize = getSize();
refresh(oldSize);
}
public List<IDIYComponent<?>> getComponents() {
return components;
}
public Sort getSort() {
return sort;
}
public void setSort(Sort sort) {
this.sort = sort;
refresh(getSize());
}
public void setSearchText(String searchText) {
this.searchText = searchText.toLowerCase();
refresh(getSize());
}
private void refresh(int oldSize) {
if (sort == Sort.NAME) {
this.components =
componentsRaw.stream().sorted(Comparator.comparing(x -> x.toString().toLowerCase()))
.collect(Collectors.toList());
} else if (sort == Sort.TYPE) {
this.components = componentsRaw.stream()
.sorted(Comparator.comparing(x -> x.getClass().getName().toLowerCase())
.thenComparing(x -> x.toString().toLowerCase()))
.collect(Collectors.toList());
} else {
List<IDIYComponent<?>> componentsClone = new ArrayList<IDIYComponent<?>>(componentsRaw);
Collections.reverse(componentsClone);
this.components = componentsClone;
}
if (searchText != null && !searchText.trim().isEmpty()) {
this.components = this.components.stream().filter(component -> {
return component.getName().toLowerCase().contains(searchText);
}).collect(Collectors.toList());
}
int newSize = this.components.size();
fireContentsChanged(this, 0, Math.min(newSize, oldSize));
if (oldSize > newSize)
fireIntervalRemoved(this, newSize, oldSize);
else if (newSize > oldSize)
fireIntervalAdded(this, oldSize, newSize);
}
@Override
public int getSize() {
return this.components == null ? 0 : this.components.size();
}
@Override
public IDIYComponent<?> getElementAt(int index) {
return this.components.get(index);
}
}
private static class ComponentListCellRenderer implements ListCellRenderer<IDIYComponent<?>> {
// private static Color[] layerColors = new Color[] { Color.gray, Color.decode("#ff6363"),
// Color.decode("#293462"), Color.decode("#138588"),
// Color.decode("#c0ca03"), Color.decode("#990000") };
protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
private Map<String, ComponentType> componentTypes;
public ComponentListCellRenderer(Map<String, List<ComponentType>> componentTypeMap) {
this.componentTypes = componentTypeMap.values().stream().flatMap(x -> x.stream())
.collect(Collectors.toMap(x -> x.getInstanceClass().getCanonicalName(), x -> x));
}
@Override
public Component getListCellRendererComponent(JList<? extends IDIYComponent<?>> list,
IDIYComponent<?> value, int index, boolean isSelected, boolean cellHasFocus) {
ComponentType componentType = componentTypes.get(value.getClass().getCanonicalName());
JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index,
isSelected, cellHasFocus);
int layerId = (int) Math.round(componentType.getZOrder());
String valueForDisplay = value.getValueForDisplay();
if (isSelected) {
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
renderer.setText(String.format("[L%s] %s", layerId, value.getName()));
} else {
renderer
.setText(String.format("[L%s] %s (%s)", layerId, value.getName(), valueForDisplay));
}
} else {
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
renderer.setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
layerId, value.getName()));
} else {
renderer.setText(String.format(
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
layerId, value.getName(), valueForDisplay));
List<TreePath> pathsToSelect = new ArrayList<>();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) getComponentTreeModel().getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
Object userObject = child.getUserObject();
if (userObject instanceof IDIYComponent && selectedComponents.contains(userObject)) {
pathsToSelect.add(new TreePath(child.getPath()));
}
}
return renderer;
tree.getSelectionModel().setSelectionPaths(pathsToSelect.toArray(new TreePath[0]));
// If none of the selected items is visible, scroll to the first one
if (!pathsToSelect.isEmpty()) {
TreePath firstPath = pathsToSelect.get(0);
if (tree.getRowForPath(firstPath) == -1) {
tree.scrollPathToVisible(firstPath);
}
}
}
}
private static enum Sort {
static enum Sort {
Z_INDEX("Sort by Z-index"), NAME("Sort by Name"), TYPE("Sort by Type");
private String label;