mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3e80d7472 | |||
| 06e836b1ed | |||
| a7bb1b02bf | |||
| a8e8e72458 | |||
| 7da4444c18 |
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-core</artifactId>
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
/**
|
||||
@@ -44,19 +45,21 @@ public class ComponentTransferable implements Transferable, Serializable {
|
||||
public static final DataFlavor listFlavor = new DataFlavor(ComponentTransferable.class, "application/diylc");
|
||||
|
||||
private List<IDIYComponent<?>> components;
|
||||
private Set<Set<IDIYComponent<?>>> groups;
|
||||
private Set<ComponentGroup> groups;
|
||||
private String name;
|
||||
|
||||
public ComponentTransferable() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components) {
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, String name) {
|
||||
super();
|
||||
this.components = components;
|
||||
this.name = name;
|
||||
this.groups = null;
|
||||
}
|
||||
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, Set<Set<IDIYComponent<?>>> groups) {
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, Set<ComponentGroup> groups) {
|
||||
super();
|
||||
this.components = components;
|
||||
this.groups = groups;
|
||||
@@ -84,7 +87,11 @@ public class ComponentTransferable implements Transferable, Serializable {
|
||||
return components;
|
||||
}
|
||||
|
||||
public Set<Set<IDIYComponent<?>>> getGroups() {
|
||||
public Set<ComponentGroup> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-25
@@ -17,15 +17,11 @@
|
||||
*/
|
||||
package org.diylc.clipboard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class ComponentTransferableFactory {
|
||||
@@ -40,13 +36,19 @@ public class ComponentTransferableFactory {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ComponentTransferable build(Collection<IDIYComponent<?>> selectedComponents, Set<Set<IDIYComponent<?>>> groups) {
|
||||
Set<IDIYComponent<?>> originalComponentSet = new HashSet<IDIYComponent<?>>(selectedComponents);
|
||||
public ComponentTransferable build(Collection<IDIYComponent<?>> selectedComponents, Set<ComponentGroup> groups) {
|
||||
|
||||
List<IDIYComponent<?>> originalComponents = new ArrayList<IDIYComponent<?>>(selectedComponents);
|
||||
|
||||
Map<UUID, UUID> idMap = new HashMap<>();
|
||||
|
||||
List<IDIYComponent<?>> clonedComponents = originalComponents.stream(
|
||||
).map(x -> {
|
||||
try {
|
||||
return x.clone();
|
||||
IDIYComponent<?> clone = x.clone();
|
||||
clone.setId(UUID.randomUUID());
|
||||
idMap.put(x.getId(), clone.getId());
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
@@ -54,22 +56,11 @@ public class ComponentTransferableFactory {
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
Set<Set<IDIYComponent<?>>> clonedGroups = new HashSet<Set<IDIYComponent<?>>>();
|
||||
|
||||
for (Set<IDIYComponent<?>> group : groups) {
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
|
||||
if (originalComponentSet.contains(group.iterator().next())) {
|
||||
Set<IDIYComponent<?>> clonedGroup = new HashSet<IDIYComponent<?>>();
|
||||
clonedGroups.add(clonedGroup);
|
||||
for (int i = 0; i < originalComponents.size(); i++) {
|
||||
if (group.contains(originalComponents.get(i))) {
|
||||
clonedGroup.add(clonedComponents.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<ComponentGroup> clonedGroups = groups.stream().map(group ->
|
||||
new ComponentGroup(group.getComponentIds().stream()
|
||||
.map(idMap::get).collect(Collectors.toSet()), group.getName()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return new ComponentTransferable(clonedComponents, clonedGroups);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
DIY Layout Creator (DIYLC).
|
||||
Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
|
||||
This file is part of DIYLC.
|
||||
|
||||
DIYLC is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
DIYLC is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
package org.diylc.common;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
|
||||
/**
|
||||
* Interface for component grouping operations.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public interface IGroupProcessor {
|
||||
|
||||
/**
|
||||
* Returns the set of component groups in the current project. Each group is a set of components
|
||||
* that are grouped together.
|
||||
*
|
||||
* @return set of component groups
|
||||
*/
|
||||
Set<ComponentGroup> getComponentGroups();
|
||||
|
||||
/**
|
||||
* Groups all selected components.
|
||||
*/
|
||||
void groupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Ungroups all selected components.
|
||||
*/
|
||||
void ungroupSelectedComponents();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.diylc.core.ExpansionMode;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.measures.Size;
|
||||
|
||||
public interface ISelectionProcessor {
|
||||
public interface ISelectionProcessor extends IGroupProcessor {
|
||||
|
||||
/**
|
||||
* Returns the current {@link ComponentTransferable}.
|
||||
@@ -89,16 +89,6 @@ public interface ISelectionProcessor {
|
||||
*/
|
||||
void rotateSelection(int direction);
|
||||
|
||||
/**
|
||||
* Groups all selected components.
|
||||
*/
|
||||
void groupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Ungroups all selected components.
|
||||
*/
|
||||
void ungroupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Mirrors selected components in the given axis.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -52,6 +53,7 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected UUID id = UUID.randomUUID();
|
||||
protected String name = "";
|
||||
|
||||
public static Color SELECTION_COLOR = Color.red;
|
||||
@@ -78,6 +80,19 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
if (id == null) {
|
||||
id = UUID.randomUUID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canControlPointOverlap(int index) {
|
||||
return false;
|
||||
@@ -186,6 +201,10 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(this);
|
||||
|
||||
if (field.getName().equalsIgnoreCase("id")) {
|
||||
value = getId();
|
||||
}
|
||||
|
||||
// Deep copy point arrays.
|
||||
// TODO: something nicer
|
||||
if (value != null && value.getClass().isArray()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.diylc.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ComponentGroup implements Serializable {
|
||||
|
||||
private Set<UUID> componentIds;
|
||||
private String name;
|
||||
|
||||
public ComponentGroup(Set<UUID> componentIds) {
|
||||
this.componentIds = componentIds;
|
||||
}
|
||||
|
||||
public ComponentGroup(Set<UUID> componentIds, String name) {
|
||||
this.componentIds = componentIds;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static ComponentGroup from(Collection<IDIYComponent<?>> components, String name) {
|
||||
return new ComponentGroup(components.stream()
|
||||
.map(IDIYComponent::getId).collect(Collectors.toSet()), name);
|
||||
}
|
||||
|
||||
public Set<UUID> getComponentIds() {
|
||||
return componentIds;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
@@ -55,6 +56,10 @@ public interface IDIYComponent<T> extends Serializable, Cloneable {
|
||||
public static final int WIRING = 5;
|
||||
public static final int TEXT = 6;
|
||||
|
||||
UUID getId();
|
||||
|
||||
void setId(UUID id);
|
||||
|
||||
/**
|
||||
* @return component instance name.
|
||||
*/
|
||||
|
||||
@@ -23,13 +23,9 @@ package org.diylc.core;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.diylc.appframework.update.VersionNumber;
|
||||
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
@@ -67,6 +63,7 @@ public class Project implements Serializable, Cloneable {
|
||||
private Integer dotSpacing;
|
||||
private List<IDIYComponent<?>> components;
|
||||
private Set<Set<IDIYComponent<?>>> groups;
|
||||
private Set<ComponentGroup> groupsEx;
|
||||
private Set<Integer> lockedLayers;
|
||||
private Set<IDIYComponent<?>> lockedComponents;
|
||||
private Set<Integer> hiddenLayers;
|
||||
@@ -75,6 +72,7 @@ public class Project implements Serializable, Cloneable {
|
||||
public Project() {
|
||||
components = new ArrayList<IDIYComponent<?>>();
|
||||
groups = new HashSet<Set<IDIYComponent<?>>>();
|
||||
groupsEx = new HashSet<>();
|
||||
lockedLayers = new HashSet<Integer>();
|
||||
hiddenLayers = new HashSet<Integer>();
|
||||
lockedComponents = new HashSet<IDIYComponent<?>>();
|
||||
@@ -153,22 +151,28 @@ public class Project implements Serializable, Cloneable {
|
||||
|
||||
/**
|
||||
* List of components sorted by z-order ascending.
|
||||
*
|
||||
* s
|
||||
* @return
|
||||
*/
|
||||
public List<IDIYComponent<?>> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of grouped components.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Set<IDIYComponent<?>>> getGroups() {
|
||||
return groups;
|
||||
public Set<ComponentGroup> getGroupsEx() {
|
||||
if (groupsEx == null) {
|
||||
groupsEx = new HashSet<>();
|
||||
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
for (Set<IDIYComponent<?>> group : groups) {
|
||||
ComponentGroup newGroup = ComponentGroup.from(group, null);
|
||||
groupsEx.add(newGroup);
|
||||
}
|
||||
groups.clear();
|
||||
}
|
||||
}
|
||||
return groupsEx;
|
||||
}
|
||||
|
||||
|
||||
public Set<IDIYComponent<?>> getLockedComponents() {
|
||||
if (lockedComponents == null)
|
||||
lockedComponents = new HashSet<IDIYComponent<?>>();
|
||||
@@ -292,6 +296,11 @@ public class Project implements Serializable, Cloneable {
|
||||
return false;
|
||||
} else if (!groups.equals(other.groups))
|
||||
return false;
|
||||
if (groupsEx == null) {
|
||||
if (other.groupsEx != null)
|
||||
return false;
|
||||
} else if (!groupsEx.equals(other.groupsEx))
|
||||
return false;
|
||||
if (lockedLayers == null) {
|
||||
if (other.lockedLayers != null)
|
||||
return false;
|
||||
@@ -356,12 +365,8 @@ public class Project implements Serializable, Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
for (Set<IDIYComponent<?>> group : this.groups) {
|
||||
Set<IDIYComponent<?>> cloneGroup = new HashSet<IDIYComponent<?>>();
|
||||
for (IDIYComponent<?> component : group) {
|
||||
cloneGroup.add(cloneMap.get(component));
|
||||
}
|
||||
project.groups.add(cloneGroup);
|
||||
for (ComponentGroup group : this.getGroupsEx()) {
|
||||
project.getGroupsEx().add(new ComponentGroup(group.getComponentIds(), group.getName()));
|
||||
}
|
||||
|
||||
for (IDIYComponent<?> component : this.getLockedComponents()) {
|
||||
|
||||
@@ -24,13 +24,7 @@ package org.diylc.presenter;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
@@ -68,9 +62,9 @@ public class InstantiationManager {
|
||||
public static final ComponentType clipboardType = new ComponentType("Clipboard contents",
|
||||
"Components from the clipboard", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
public static final ComponentType blockType = new ComponentType("Building block",
|
||||
"Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
// public static final ComponentType blockType = new ComponentType("Building block",
|
||||
// "Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
// false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
|
||||
public InstantiationManager() {}
|
||||
|
||||
@@ -213,7 +207,10 @@ public class InstantiationManager {
|
||||
|
||||
// Update the component type slot so the app knows that something's
|
||||
// being instantiated.
|
||||
this.componentTypeSlot = autoGroup ? blockType : clipboardType;
|
||||
String slotName = Optional.ofNullable(componentTransferable.getName()).orElse("Building Block");
|
||||
this.componentTypeSlot = autoGroup ? new ComponentType(slotName,
|
||||
"Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0) : clipboardType;
|
||||
|
||||
if (snapToGrid) {
|
||||
scaledPoint = new Point2D.Double(scaledPoint.getX(), scaledPoint.getY());
|
||||
@@ -224,7 +221,7 @@ public class InstantiationManager {
|
||||
|
||||
// copy group information if available
|
||||
if (componentTransferable.getGroups() != null) {
|
||||
currentProject.getGroups().addAll(componentTransferable.getGroups());
|
||||
currentProject.getGroupsEx().addAll(componentTransferable.getGroups());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,17 +32,7 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.JOptionPane;
|
||||
@@ -553,7 +543,7 @@ public class Presenter implements IPlugInPort {
|
||||
// group components if there's more than one, e.g. building blocks, but not clipboard
|
||||
// contents
|
||||
if (componentSlot.size() > 1 && !componentTypeSlot.getName().toLowerCase().contains("clipboard")) {
|
||||
this.currentProject.getGroups().add(new HashSet<IDIYComponent<?>>(componentSlot));
|
||||
this.currentProject.getGroupsEx().add(ComponentGroup.from(componentSlot, componentTypeSlot.getName()));
|
||||
}
|
||||
|
||||
notifyProjectModifiedIfNeeded(oldProject, "Add " + componentTypeSlot.getName(), true, true);
|
||||
@@ -1727,11 +1717,32 @@ public class Presenter implements IPlugInPort {
|
||||
@Override
|
||||
public void groupSelectedComponents() {
|
||||
LOG.info("groupSelectedComponents()");
|
||||
|
||||
// Prompt user for group name
|
||||
String groupName = view.showInputDialog("Enter group name:", "Group Components");
|
||||
|
||||
// If user cancelled, don't create the group
|
||||
if (groupName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Project oldProject = currentProject.clone();
|
||||
// First remove the selected components from other groups.
|
||||
// First break any existing groups that contain any of the selected components.
|
||||
// This ensures that if components A and B are in a group, and the user selects
|
||||
// A, B, and C to group together, the old group of A and B is deleted and replaced
|
||||
// with a new group containing A, B, and C.
|
||||
ungroupComponents(selectedComponents);
|
||||
// Then group them together.
|
||||
currentProject.getGroups().add(new HashSet<IDIYComponent<?>>(selectedComponents));
|
||||
// Then create a new group containing all selected components with the provided name.
|
||||
// Filter out any null IDs to prevent creating groups with null component IDs.
|
||||
Set<UUID> componentIds = selectedComponents.stream()
|
||||
.map(IDIYComponent::getId)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Only create the group if there are valid component IDs
|
||||
if (!componentIds.isEmpty()) {
|
||||
currentProject.getGroupsEx().add(new ComponentGroup(componentIds, groupName));
|
||||
}
|
||||
// Notify the listeners.
|
||||
notifyProjectModifiedIfNeeded(oldProject, "Group", false, true);
|
||||
}
|
||||
@@ -2186,14 +2197,11 @@ public class Presenter implements IPlugInPort {
|
||||
* @param components
|
||||
*/
|
||||
private void ungroupComponents(Collection<IDIYComponent<?>> components) {
|
||||
Iterator<Set<IDIYComponent<?>>> groupIterator = currentProject.getGroups().iterator();
|
||||
while (groupIterator.hasNext()) {
|
||||
Set<IDIYComponent<?>> group = groupIterator.next();
|
||||
group.removeAll(components);
|
||||
if (group.isEmpty()) {
|
||||
groupIterator.remove();
|
||||
}
|
||||
}
|
||||
Set<UUID> componentIds = components.stream().map(IDIYComponent::getId).collect(Collectors.toSet());
|
||||
// Remove the group if it contains any of the specified components
|
||||
currentProject.getGroupsEx().removeIf(
|
||||
group -> group.getComponentIds() != null && group.getComponentIds().stream()
|
||||
.anyMatch(id -> id != null && componentIds.contains(id)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2205,15 +2213,16 @@ public class Presenter implements IPlugInPort {
|
||||
* the minimum, set contains that single component.
|
||||
*/
|
||||
private Set<IDIYComponent<?>> findAllGroupedComponents(IDIYComponent<?> component) {
|
||||
Set<IDIYComponent<?>> components = new HashSet<IDIYComponent<?>>();
|
||||
components.add(component);
|
||||
for (Set<IDIYComponent<?>> group : currentProject.getGroups()) {
|
||||
if (group.contains(component)) {
|
||||
components.addAll(group);
|
||||
break;
|
||||
|
||||
for (ComponentGroup group : currentProject.getGroupsEx()) {
|
||||
if (group.getComponentIds().stream().anyMatch(id -> Objects.equals(id, component.getId()))) {
|
||||
return currentProject.getComponents().stream()
|
||||
.filter(comp -> group.getComponentIds().contains(comp.getId()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
return components;
|
||||
|
||||
return new HashSet<>(Set.of(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2747,7 +2756,7 @@ public class Presenter implements IPlugInPort {
|
||||
@Override
|
||||
public void loadBlock(String blockName) throws InvalidBlockException {
|
||||
List<IDIYComponent<?>> components = buildingBlockManager.loadBlock(blockName, currentProject.getComponents());
|
||||
pasteComponents(new ComponentTransferable(components), true, true);
|
||||
pasteComponents(new ComponentTransferable(components, blockName), true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2912,4 +2921,9 @@ public class Presenter implements IPlugInPort {
|
||||
public DrawingManager getDrawingManager() {
|
||||
return drawingManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ComponentGroup> getComponentGroups() {
|
||||
return currentProject.getGroupsEx();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ public class ProjectFileManager {
|
||||
xStream.autodetectAnnotations(true);
|
||||
xStream.alias("point", java.awt.geom.Point2D.class);
|
||||
xStream.alias("point", java.awt.geom.Point2D.Double.class);
|
||||
xStream.alias("group", org.diylc.core.ComponentGroup.class);
|
||||
xStream.alias("font", java.awt.Font.class);
|
||||
xStream.alias("project", Project.class);
|
||||
xStream.aliasPackage("diylc", "org.diylc.components");
|
||||
|
||||
@@ -490,9 +490,17 @@
|
||||
<minor>9</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-12-30 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2026-01-10 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to name groups of components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Show groups of components as single items in the Project Explorer list</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added balloon tooltip next to cursor showing component width, height and length (where available) while resizing; can be disabled from Config menu</description>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-library</artifactId>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-swing</artifactId>
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CopyAction extends AbstractAction {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionFactory.LOG.info("Copy triggered");
|
||||
clipboard.setContents(ComponentTransferableFactory.getInstance()
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroupsEx()),
|
||||
clipboardOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CutAction extends AbstractAction {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionFactory.LOG.info("Cut triggered");
|
||||
clipboard.setContents(ComponentTransferableFactory.getInstance()
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroupsEx()),
|
||||
clipboardOwner);
|
||||
plugInPort.deleteSelectedComponents();
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ImportAction extends AbstractAction {
|
||||
// presenter
|
||||
plugInPort.pasteComponents(ComponentTransferableFactory.getInstance().build(
|
||||
presenter.getCurrentProject().getComponents(),
|
||||
presenter.getCurrentProject().getGroups()), false, false);
|
||||
presenter.getCurrentProject().getGroupsEx()), false, false);
|
||||
// Cleanup components in the temp presenter, don't need
|
||||
// them anymore
|
||||
presenter.selectAll(0);
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ public class ImportNetlistAction extends AbstractAction {
|
||||
if (!outputWarnings.isEmpty())
|
||||
ActionFactory.LOG.warn("Component creation produced warnings:\n"
|
||||
+ String.join("\n", outputWarnings));
|
||||
plugInPort.pasteComponents(new ComponentTransferable(components), false, false);
|
||||
plugInPort.pasteComponents(new ComponentTransferable(components, "Imported Netlist"), false, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
swingUI.showMessage("Could not import netlist file: " + e.getMessage(), "Error",
|
||||
@@ -130,4 +130,4 @@ public class ImportNetlistAction extends AbstractAction {
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
-266
@@ -1,266 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+517
-112
@@ -1,5 +1,6 @@
|
||||
package org.diylc.swing.plugins.explorer;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
@@ -9,36 +10,49 @@ 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 javax.swing.DropMode;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.AbstractListModel;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
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.JTree;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
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 javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.core.ComponentGroup;
|
||||
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;
|
||||
@@ -47,10 +61,11 @@ public class ExplorerPane extends JPanel {
|
||||
private JLabel titleLabel;
|
||||
private JTextField searchField;
|
||||
private JScrollPane componentScrollPane;
|
||||
private ComponentTreeModel componentTreeModel;
|
||||
private JTree componentTree;
|
||||
private ComponentListModel componentListModel;
|
||||
private JList<ComponentGroupItem> componentList;
|
||||
private ComponentPopupMenu popupMenu;
|
||||
private JComboBox<Sort> sortBox;
|
||||
private ListSelectionListener selectionListener;
|
||||
|
||||
public ExplorerPane(ISwingUI swingUI, IPlugInPort plugInPort) {
|
||||
super();
|
||||
@@ -104,7 +119,7 @@ public class ExplorerPane extends JPanel {
|
||||
public JTextField getSearchField() {
|
||||
if (searchField == null) {
|
||||
searchField = new SearchTextField(true, 'X', text -> {
|
||||
getComponentTreeModel().setSearchText(text);
|
||||
getComponentListModel().setSearchText(text);
|
||||
});
|
||||
}
|
||||
return searchField;
|
||||
@@ -117,7 +132,7 @@ public class ExplorerPane extends JPanel {
|
||||
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
getComponentTreeModel().setSort((Sort) e.getItem());
|
||||
getComponentListModel().setSort((Sort) e.getItem());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -126,45 +141,129 @@ public class ExplorerPane extends JPanel {
|
||||
|
||||
public JScrollPane getComponentScrollPane() {
|
||||
if (componentScrollPane == null) {
|
||||
componentScrollPane = new JScrollPane(getComponentTree(),
|
||||
componentScrollPane = new JScrollPane(getComponentList(),
|
||||
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
}
|
||||
return componentScrollPane;
|
||||
}
|
||||
|
||||
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() {
|
||||
public JList<ComponentGroupItem> getComponentList() {
|
||||
if (componentList == null) {
|
||||
componentList = new DragDropList<ComponentGroupItem>(new IDragDropListListener() {
|
||||
|
||||
@Override
|
||||
public void valueChanged(TreeSelectionEvent e) {
|
||||
if (e.getNewLeadSelectionPath() == null)
|
||||
return;
|
||||
|
||||
List<IDIYComponent<?>> selectedValuesList = getSelectedComponents();
|
||||
if (selectedValuesList.size() > 0 && !new HashSet<IDIYComponent<?>>(selectedValuesList)
|
||||
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents()))) {
|
||||
plugInPort.setSelection(selectedValuesList, true);
|
||||
public boolean dropComplete(int selectedIndex, int dropTargetIndex) {
|
||||
if (getComponentListModel().getSort() != Sort.Z_INDEX) {
|
||||
swingUI.showMessage(REORDERING_NOT_POSSIBLE, "Error", ISwingUI.ERROR_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
// The list is displayed in reverse order (highest z-index first, index 0 = last in component list)
|
||||
// Component list is in normal order (lowest z-index first, index 0 = first component)
|
||||
// In INSERT mode, dropTargetIndex is where the item will be inserted AFTER removal
|
||||
int listSize = getComponentListModel().getSize();
|
||||
int componentCount = plugInPort.getCurrentProject().getComponents().size();
|
||||
|
||||
// The list size may be different from component count due to groups
|
||||
// listSize = componentCount - groupedComponents + groups
|
||||
// We need to find which component will be at dropTargetIndex in the final list
|
||||
// and convert that to a component index
|
||||
|
||||
// Find the ComponentGroupItem that will be at dropTargetIndex after removal
|
||||
// We want to insert before the component that will be at dropTargetIndex after insertion
|
||||
List<ComponentGroupItem> components = getComponentListModel().getComponents();
|
||||
|
||||
int componentAtTargetListIndex;
|
||||
|
||||
if (dropTargetIndex >= listSize - 1) {
|
||||
// Dropping at the end
|
||||
plugInPort.moveSelectionToZIndex(componentCount);
|
||||
return true;
|
||||
} else if (selectedIndex < dropTargetIndex) {
|
||||
// Dragging up: removal happens before target
|
||||
// After removal, the component that was at dropTargetIndex is now at dropTargetIndex-1
|
||||
// After insertion at dropTargetIndex, it will be at dropTargetIndex+1
|
||||
// So we want to insert before the component currently at dropTargetIndex
|
||||
componentAtTargetListIndex = dropTargetIndex;
|
||||
} else {
|
||||
// Dragging down: removal happens after target
|
||||
// After removal, the component at dropTargetIndex stays at dropTargetIndex
|
||||
// After insertion at dropTargetIndex, it will be at dropTargetIndex+1
|
||||
// So we want to insert before the component currently at dropTargetIndex
|
||||
componentAtTargetListIndex = dropTargetIndex;
|
||||
}
|
||||
|
||||
if (componentAtTargetListIndex >= listSize) {
|
||||
// Insert at the end
|
||||
plugInPort.moveSelectionToZIndex(componentCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the ComponentGroupItem at that position
|
||||
ComponentGroupItem targetItem = components.get(componentAtTargetListIndex);
|
||||
|
||||
// Find the highest z-index component in this group (or the component itself if single)
|
||||
// Since the list is sorted by z-index (reversed), this is the component that determines
|
||||
// the position of this group in the list
|
||||
IDIYComponent<?> targetComponent = null;
|
||||
int maxComponentIndex = -1;
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
|
||||
for (IDIYComponent<?> comp : targetItem.getGroup()) {
|
||||
int compIndex = allComponents.indexOf(comp);
|
||||
if (compIndex > maxComponentIndex) {
|
||||
maxComponentIndex = compIndex;
|
||||
targetComponent = comp;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetComponent == null || maxComponentIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to insert before the component at maxComponentIndex
|
||||
// moveSelectionToZIndex will automatically decrement zIndex for each selected component
|
||||
// that's before the target, so we can use maxComponentIndex directly
|
||||
// However, we need to add 1 because we want to insert at dropTargetIndex, not before it
|
||||
int targetComponentIndex = maxComponentIndex + 1;
|
||||
|
||||
if (targetComponentIndex < 0) {
|
||||
targetComponentIndex = 0;
|
||||
} else if (targetComponentIndex > componentCount) {
|
||||
targetComponentIndex = componentCount;
|
||||
}
|
||||
|
||||
plugInPort.moveSelectionToZIndex(targetComponentIndex);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
componentList.setModel(getComponentListModel());
|
||||
componentList.setFont(DEFAULT_FONT);
|
||||
componentList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
componentList.setCellRenderer(new ComponentListCellRenderer(plugInPort.getComponentTypes()));
|
||||
selectionListener = new ListSelectionListener() {
|
||||
|
||||
componentTree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting())
|
||||
return;
|
||||
|
||||
List<ComponentGroupItem> selectedValuesList = componentList.getSelectedValuesList();
|
||||
Set<IDIYComponent<?>> expandedSelection = new HashSet<>();
|
||||
|
||||
for (ComponentGroupItem selected : selectedValuesList) {
|
||||
// Always add all components from the group item
|
||||
expandedSelection.addAll(selected.getGroup());
|
||||
}
|
||||
|
||||
if (expandedSelection.size() > 0 && !expandedSelection
|
||||
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents()))) {
|
||||
plugInPort.setSelection(expandedSelection, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
componentList.addListSelectionListener(selectionListener);
|
||||
|
||||
componentList.addMouseListener(new MouseAdapter() {
|
||||
|
||||
private MouseEvent pressedEvent;
|
||||
|
||||
@@ -177,7 +276,7 @@ public class ExplorerPane extends JPanel {
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
componentTree.requestFocus();
|
||||
componentList.requestFocus();
|
||||
pressedEvent = e;
|
||||
}
|
||||
|
||||
@@ -192,8 +291,13 @@ public class ExplorerPane extends JPanel {
|
||||
if (plugInPort.getNewComponentTypeSlot() == null && (e.isPopupTrigger()
|
||||
|| (pressedEvent != null && pressedEvent.isPopupTrigger()))) {
|
||||
|
||||
final List<IDIYComponent<?>> selectedValues = getSelectedComponents();
|
||||
getComponentPopupMenu().prepareAndShowAt(selectedValues, selectedValues, e.getX(),
|
||||
final List<ComponentGroupItem> selectedValues = componentList.getSelectedValuesList();
|
||||
// Extract actual components from selected items
|
||||
List<IDIYComponent<?>> components = new ArrayList<>();
|
||||
for (ComponentGroupItem item : selectedValues) {
|
||||
components.addAll(item.getGroup());
|
||||
}
|
||||
getComponentPopupMenu().prepareAndShowAt(components, components, e.getX(),
|
||||
e.getY());
|
||||
}
|
||||
}
|
||||
@@ -201,103 +305,356 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
});
|
||||
}
|
||||
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;
|
||||
return componentList;
|
||||
}
|
||||
|
||||
public ComponentTreeModel getComponentTreeModel() {
|
||||
if (componentTreeModel == null) {
|
||||
componentTreeModel = new ComponentTreeModel();
|
||||
public ComponentListModel getComponentListModel() {
|
||||
if (componentListModel == null) {
|
||||
componentListModel = new ComponentListModel();
|
||||
}
|
||||
return componentTreeModel;
|
||||
return componentListModel;
|
||||
}
|
||||
|
||||
public ComponentPopupMenu getComponentPopupMenu() {
|
||||
if (popupMenu == null) {
|
||||
popupMenu = new ComponentPopupMenu(plugInPort, getComponentTree(), false);
|
||||
popupMenu = new ComponentPopupMenu(plugInPort, getComponentList(), false);
|
||||
}
|
||||
return popupMenu;
|
||||
}
|
||||
|
||||
public void setComponents(List<IDIYComponent<?>> components,
|
||||
Collection<IDIYComponent<?>> selectedComponents) {
|
||||
// Get groups from the current project
|
||||
Set<Set<IDIYComponent<?>>> groups = null;
|
||||
if (plugInPort.getCurrentProject() != null) {
|
||||
groups = plugInPort.getCurrentProject().getGroups();
|
||||
}
|
||||
this.getComponentTreeModel().setComponents(components, groups);
|
||||
this.getComponentListModel().setComponents(components);
|
||||
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<?>> currentSelected = getSelectedComponents();
|
||||
if (new HashSet<IDIYComponent<?>>(currentSelected)
|
||||
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents())))
|
||||
List<ComponentGroupItem> selectedValuesList = componentList.getSelectedValuesList();
|
||||
|
||||
// Extract components from selected values
|
||||
Set<IDIYComponent<?>> currentSelected = new HashSet<>();
|
||||
for (ComponentGroupItem item : selectedValuesList) {
|
||||
currentSelected.addAll(item.getGroup());
|
||||
}
|
||||
|
||||
if (currentSelected.equals(selectedComponents))
|
||||
return;
|
||||
|
||||
final JTree tree = this.getComponentTree();
|
||||
final JList<ComponentGroupItem> list = this.getComponentList();
|
||||
|
||||
if (selectedComponents.size() == 0) {
|
||||
tree.clearSelection();
|
||||
} else {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// Temporarily remove listener to prevent infinite loop
|
||||
list.removeListSelectionListener(selectionListener);
|
||||
try {
|
||||
if (selectedComponents.size() == 0) {
|
||||
list.clearSelection();
|
||||
} else {
|
||||
// Build a map from component to its group (if any)
|
||||
Set<ComponentGroup> groups = plugInPort.getComponentGroups();
|
||||
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> componentToGroup = new java.util.HashMap<>();
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
// Build a map from UUID to component for quick lookup
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
Map<UUID, IDIYComponent<?>> idToComponent = allComponents.stream()
|
||||
.collect(Collectors.toMap(IDIYComponent::getId, comp -> comp));
|
||||
|
||||
for (ComponentGroup group : groups) {
|
||||
// Convert ComponentGroup (with UUIDs) to Set<IDIYComponent<?>>
|
||||
Set<IDIYComponent<?>> componentSet = group.getComponentIds().stream()
|
||||
.map(idToComponent::get)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (IDIYComponent<?> component : componentSet) {
|
||||
componentToGroup.put(component, componentSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find indices of items to select
|
||||
List<ComponentGroupItem> components = this.getComponentListModel().getComponents();
|
||||
Set<Integer> selectedIndices = new HashSet<>();
|
||||
Set<Set<IDIYComponent<?>>> selectedGroups = new HashSet<>();
|
||||
|
||||
for (IDIYComponent<?> selected : selectedComponents) {
|
||||
Set<IDIYComponent<?>> group = componentToGroup.get(selected);
|
||||
if (group != null) {
|
||||
// Component is in a group
|
||||
if (!selectedGroups.contains(group)) {
|
||||
// Find the ComponentGroupItem for this group
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
ComponentGroupItem groupItem = components.get(i);
|
||||
if (groupItem.getGroup().equals(group)) {
|
||||
selectedIndices.add(i);
|
||||
selectedGroups.add(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Component is not in any group, find the ComponentGroupItem that contains only this component
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
ComponentGroupItem groupItem = components.get(i);
|
||||
if (groupItem.getGroup().size() == 1 && groupItem.getGroup().contains(selected)) {
|
||||
selectedIndices.add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] indices = selectedIndices.stream().mapToInt(i -> i).toArray();
|
||||
|
||||
list.getSelectionModel().setValueIsAdjusting(true);
|
||||
list.setSelectedIndices(indices);
|
||||
list.getSelectionModel().setValueIsAdjusting(false);
|
||||
if (indices.length > 0) {
|
||||
int firstVisibleIndex = list.getFirstVisibleIndex();
|
||||
int lastVisibleIndex = list.getLastVisibleIndex();
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Re-add listener
|
||||
list.addListSelectionListener(selectionListener);
|
||||
}
|
||||
}
|
||||
|
||||
private class ComponentListModel extends AbstractListModel<ComponentGroupItem> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
static enum Sort {
|
||||
private List<IDIYComponent<?>> componentsRaw;
|
||||
private List<ComponentGroupItem> 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<ComponentGroupItem> 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) {
|
||||
List<IDIYComponent<?>> sortedComponents;
|
||||
|
||||
if (sort == Sort.NAME) {
|
||||
sortedComponents =
|
||||
componentsRaw.stream().sorted(Comparator.comparing(x -> x.toString().toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
} else if (sort == Sort.TYPE) {
|
||||
sortedComponents = 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);
|
||||
sortedComponents = componentsClone;
|
||||
}
|
||||
|
||||
if (searchText != null && !searchText.trim().isEmpty()) {
|
||||
sortedComponents = sortedComponents.stream().filter(component -> {
|
||||
return component.getName().toLowerCase().contains(searchText);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Build list with groups and ungrouped components
|
||||
this.components = buildComponentListWithGroups(sortedComponents);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the component list, using ComponentGroupItem for all items.
|
||||
* For grouped components, creates one ComponentGroupItem per group.
|
||||
* For ungrouped components, creates a ComponentGroupItem with a single component.
|
||||
*/
|
||||
private List<ComponentGroupItem> buildComponentListWithGroups(List<IDIYComponent<?>> sortedComponents) {
|
||||
Set<ComponentGroup> groups = plugInPort.getComponentGroups();
|
||||
|
||||
// Build a map from component to its group (if any) and group name
|
||||
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> componentToGroup = new java.util.HashMap<>();
|
||||
Map<Set<IDIYComponent<?>>, String> groupToName = new java.util.HashMap<>();
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
// Build a map from UUID to component for quick lookup
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
Map<UUID, IDIYComponent<?>> idToComponent = allComponents.stream()
|
||||
.collect(Collectors.toMap(IDIYComponent::getId, comp -> comp));
|
||||
|
||||
for (ComponentGroup group : groups) {
|
||||
// Convert ComponentGroup (with UUIDs) to Set<IDIYComponent<?>>
|
||||
Set<IDIYComponent<?>> componentSet = group.getComponentIds().stream()
|
||||
.map(idToComponent::get)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (IDIYComponent<?> component : componentSet) {
|
||||
componentToGroup.put(component, componentSet);
|
||||
}
|
||||
|
||||
// Store the group name
|
||||
if (group.getName() != null && !group.getName().isEmpty()) {
|
||||
groupToName.put(componentSet, group.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track which groups we've already added
|
||||
Set<Set<IDIYComponent<?>>> addedGroups = new HashSet<>();
|
||||
List<ComponentGroupItem> result = new ArrayList<>();
|
||||
|
||||
for (IDIYComponent<?> component : sortedComponents) {
|
||||
Set<IDIYComponent<?>> group = componentToGroup.get(component);
|
||||
if (group == null) {
|
||||
// Component is not in any group, create a ComponentGroupItem with just this component
|
||||
Set<IDIYComponent<?>> singleComponentGroup = new HashSet<>();
|
||||
singleComponentGroup.add(component);
|
||||
result.add(new ComponentGroupItem(singleComponentGroup, null));
|
||||
} else {
|
||||
// Component is in a group
|
||||
if (!addedGroups.contains(group)) {
|
||||
// This is the first component from this group we've seen, add the group item
|
||||
String groupName = groupToName.get(group);
|
||||
result.add(new ComponentGroupItem(group, groupName));
|
||||
addedGroups.add(group);
|
||||
}
|
||||
// Otherwise, skip this component as we already have a group item for it
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return this.components == null ? 0 : this.components.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentGroupItem getElementAt(int index) {
|
||||
return this.components.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ComponentListCellRenderer implements ListCellRenderer<ComponentGroupItem> {
|
||||
|
||||
// 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 ComponentGroupItem> list,
|
||||
ComponentGroupItem value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
|
||||
JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index,
|
||||
isSelected, cellHasFocus);
|
||||
|
||||
ComponentGroupItem groupItem = value;
|
||||
|
||||
if (groupItem.getComponentCount() == 1) {
|
||||
// Render as individual component (ungrouped)
|
||||
IDIYComponent<?> component = groupItem.getGroup().iterator().next();
|
||||
ComponentType componentType = componentTypes.get(component.getClass().getCanonicalName());
|
||||
|
||||
if (componentType == null) {
|
||||
renderer.setText(component.toString());
|
||||
return renderer;
|
||||
}
|
||||
|
||||
int layerId = (int) Math.round(componentType.getZOrder());
|
||||
|
||||
String valueForDisplay = component.getValueForDisplay();
|
||||
if (isSelected) {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("[L%s] %s", layerId, component.getName()));
|
||||
} else {
|
||||
renderer
|
||||
.setText(String.format("[L%s] %s (%s)", layerId, component.getName(), valueForDisplay));
|
||||
}
|
||||
} else {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
|
||||
layerId, component.getName()));
|
||||
} else {
|
||||
renderer.setText(String.format(
|
||||
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
|
||||
layerId, component.getName(), valueForDisplay));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Render as group
|
||||
// Calculate maximum layer from components in the group
|
||||
int maxLayerId = 0;
|
||||
for (IDIYComponent<?> component : groupItem.getGroup()) {
|
||||
ComponentType componentType = componentTypes.get(component.getClass().getCanonicalName());
|
||||
if (componentType != null) {
|
||||
int layerId = (int) Math.round(componentType.getZOrder());
|
||||
if (layerId > maxLayerId) {
|
||||
maxLayerId = layerId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format: name (component count), similar to regular components showing name (value)
|
||||
String groupName = groupItem.getGroupName() != null && !groupItem.getGroupName().isEmpty()
|
||||
? groupItem.getGroupName()
|
||||
: "Group";
|
||||
String componentCount = String.valueOf(groupItem.getComponentCount());
|
||||
|
||||
if (isSelected) {
|
||||
renderer.setText(String.format("<html>[L%s] <b>%s</b> (<b>%s</b>)</html>", maxLayerId, groupName, componentCount));
|
||||
} else {
|
||||
renderer.setText(String.format(
|
||||
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
|
||||
maxLayerId, groupName, componentCount));
|
||||
}
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
private static enum Sort {
|
||||
Z_INDEX("Sort by Z-index"), NAME("Sort by Name"), TYPE("Sort by Type");
|
||||
|
||||
private String label;
|
||||
@@ -311,4 +668,52 @@ public class ExplorerPane extends JPanel {
|
||||
return LangUtil.translate(label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class to represent a group of components in the explorer list.
|
||||
*/
|
||||
public static class ComponentGroupItem {
|
||||
private final Set<IDIYComponent<?>> group;
|
||||
private final String groupName;
|
||||
|
||||
public ComponentGroupItem(Set<IDIYComponent<?>> group, String groupName) {
|
||||
this.group = group;
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public Set<IDIYComponent<?>> getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public int getComponentCount() {
|
||||
return group.size();
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (groupName != null && !groupName.isEmpty()) {
|
||||
return groupName;
|
||||
}
|
||||
return "Group of " + group.size() + " components";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
ComponentGroupItem other = (ComponentGroupItem) obj;
|
||||
return group.equals(other.group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return group.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>DIY Layout Creator</name>
|
||||
|
||||
Reference in New Issue
Block a user