From be996d649486c3f822aadabad12e5b593187c1f5 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Wed, 7 Dec 2022 23:43:05 +0100 Subject: [PATCH] Allow drag and drop reordering in project explorer --- .../src/org/diylc/common/IPlugInPort.java | 10 -- .../org/diylc/common/ISelectionProcessor.java | 18 ++- .../src/org/diylc/presenter/Presenter.java | 36 +++++ diylc/diylc-swing/dict.txt | 1 + .../src/org/diylc/swing/gui/DragDropList.java | 129 ++++++++++++++++++ .../swing/gui/IDragDropListListener.java | 22 +++ .../swing/plugins/explorer/ExplorerPane.java | 51 +++++-- .../plugins/explorer/ExplorerPlugin.java | 6 +- 8 files changed, 247 insertions(+), 26 deletions(-) create mode 100644 diylc/diylc-swing/src/org/diylc/swing/gui/DragDropList.java create mode 100644 diylc/diylc-swing/src/org/diylc/swing/gui/IDragDropListListener.java diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index 55c920ee..1f4c7f9a 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -342,16 +342,6 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP // */ // Rectangle2D getSelectedAreaRect(); - /** - * Sends each of the selected components one step back. - */ - void sendSelectionToBack(); - - /** - * Brings each of the selected components one step to front. - */ - void bringSelectionToFront(); - /** * Causes the display to refresh. */ diff --git a/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java b/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java index 44d11c87..e0a59b95 100644 --- a/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java +++ b/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java @@ -137,5 +137,21 @@ public interface ISelectionProcessor { * * @param model */ - void applyModelToSelection(String[] model); + void applyModelToSelection(String[] model); + + /** + * Sends each of the selected components one step back. + */ + void sendSelectionToBack(); + + /** + * Brings each of the selected components one step to front. + */ + void bringSelectionToFront(); + + /** + * Move selected components to z index starting from the provided value + * @param zIndex + */ + void moveSelectionToZIndex(int zIndex); } diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index 350f824d..b09cb827 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -1946,6 +1946,42 @@ public class Presenter implements IPlugInPort { messageDispatcher.dispatchMessage(EventType.REPAINT); } } + + @Override + public void moveSelectionToZIndex(int zIndex) { + LOG.info(String.format("moveSelectionToZIndex(%d)", zIndex)); + Project oldProject = currentProject.clone(); + + // sort the selection in Z-order + List> selection = new ArrayList>(selectedComponents); + Collections.sort(selection, new Comparator>() { + + @Override + public int compare(IDIYComponent o1, IDIYComponent o2) { + return new Integer(currentProject.getComponents().indexOf(o1)) + .compareTo(currentProject.getComponents().indexOf(o2)); + } + }); + + for (IDIYComponent component : selection) { + int index = currentProject.getComponents().indexOf(component); + if (index < 0) { + LOG.warn("Component not found in the project: " + component.getName()); + } else { + if (index < zIndex) + zIndex--; + + currentProject.getComponents().remove(index); + currentProject.getComponents().add(zIndex, component); + } + } + if (!oldProject.equals(currentProject)) { + messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, currentProject, + "Change Z-order"); + projectFileManager.notifyFileChange(); + messageDispatcher.dispatchMessage(EventType.REPAINT); + } + } @Override public void refresh() { diff --git a/diylc/diylc-swing/dict.txt b/diylc/diylc-swing/dict.txt index dbe01e1e..18315e0d 100644 --- a/diylc/diylc-swing/dict.txt +++ b/diylc/diylc-swing/dict.txt @@ -55,6 +55,7 @@ Box C-B-E BJT Circle Coil(s) +Component reordering is possible only when Project Explorer is sorted by z-index Configuration Could not apply changes. Check the log for details. D-G-S JFET diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/DragDropList.java b/diylc/diylc-swing/src/org/diylc/swing/gui/DragDropList.java new file mode 100644 index 00000000..e629d82e --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/DragDropList.java @@ -0,0 +1,129 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 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 + * . + */ +package org.diylc.swing.gui; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import javax.swing.DropMode; +import javax.swing.JList; +import javax.swing.TransferHandler; + +public class DragDropList extends JList { + + private static final long serialVersionUID = 1L; + + public DragDropList(IDragDropListListener listener) { + super(); + setDragEnabled(true); + setDropMode(DropMode.INSERT); + + setTransferHandler(new MyListDropHandler(this, listener)); + + new MyDragListener(this); + } +} + +class MyDragListener implements DragSourceListener, DragGestureListener { + + DragDropList list; + DragSource ds = new DragSource(); + + public MyDragListener(DragDropList list) { + this.list = list; + ds.createDefaultDragGestureRecognizer(list, DnDConstants.ACTION_MOVE, this); + } + + public void dragGestureRecognized(DragGestureEvent dge) { + StringSelection transferable = new StringSelection(Integer.toString(list.getSelectedIndex())); + ds.startDrag(dge, DragSource.DefaultMoveDrop, transferable, this); + } + + public void dragEnter(DragSourceDragEvent dsde) {} + + public void dragExit(DragSourceEvent dse) {} + + public void dragOver(DragSourceDragEvent dsde) {} + + public void dragDropEnd(DragSourceDropEvent dsde) { +// if (dsde.getDropSuccess()) { +// System.out.println("Succeeded"); +// } else { +// System.out.println("Failed"); +// } + } + + public void dropActionChanged(DragSourceDragEvent dsde) {} +} + +class MyListDropHandler extends TransferHandler { + + private static final long serialVersionUID = 1L; + private DragDropList dragDropList; + private IDragDropListListener listener; + + public MyListDropHandler() {} + + public MyListDropHandler(DragDropList dragDropList, IDragDropListListener listener) { + this.dragDropList = dragDropList; + this.listener = listener; + } + + public boolean canImport(TransferHandler.TransferSupport support) { + if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) { + return false; + } + if (support.getComponent() != dragDropList) { + return false; + } + JList.DropLocation dl = (JList.DropLocation) support.getDropLocation(); + if (dl.getIndex() == -1) { + return false; + } else { + return true; + } + } + + public boolean importData(TransferHandler.TransferSupport support) { + if (!canImport(support)) { + return false; + } + + Transferable transferable = support.getTransferable(); + String indexString; + try { + indexString = (String) transferable.getTransferData(DataFlavor.stringFlavor); + } catch (Exception e) { + return false; + } + + int selectedIndex = Integer.parseInt(indexString); + JList.DropLocation dl = (JList.DropLocation) support.getDropLocation(); + int dropTargetIndex = dl.getIndex(); + + return listener.dropComplete(selectedIndex, dropTargetIndex); + } +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/IDragDropListListener.java b/diylc/diylc-swing/src/org/diylc/swing/gui/IDragDropListListener.java new file mode 100644 index 00000000..5ef53ad5 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/IDragDropListListener.java @@ -0,0 +1,22 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 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 + * . + */ +package org.diylc.swing.gui; + +public interface IDragDropListListener { + boolean dropComplete(int selectedIndex, int dropTargetIndex); +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPane.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPane.java index f25fc050..07c5d3f4 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPane.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPane.java @@ -10,6 +10,7 @@ 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; @@ -30,18 +31,26 @@ import org.apache.log4j.Logger; 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."); private static final Logger LOG = Logger.getLogger(ExplorerPane.class); public static Font DEFAULT_FONT = new Font("Square721 BT", Font.PLAIN, 12); + private ISwingUI swingUI; private IPlugInPort plugInPort; + private JTextField searchField; private JScrollPane componentScrollPane; private ComponentListModel componentListModel; @@ -49,8 +58,9 @@ public class ExplorerPane extends JPanel { private ComponentPopupMenu popupMenu; private JComboBox sortBox; - public ExplorerPane(IPlugInPort plugInPort) { + public ExplorerPane(ISwingUI swingUI, IPlugInPort plugInPort) { super(); + this.swingUI = swingUI; this.plugInPort = plugInPort; setName("Project Explorer"); @@ -64,14 +74,6 @@ public class ExplorerPane extends JPanel { gbc.gridwidth = 1; add(getSearchField(), gbc); - - gbc.gridy++; - - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 0; - gbc.weighty = 0; - - add(getSortBox(), gbc); gbc.gridy++; @@ -81,6 +83,14 @@ public class ExplorerPane extends JPanel { add(getComponentScrollPane(), gbc); + gbc.gridy++; + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0; + gbc.weighty = 0; + + add(getSortBox(), gbc); + setPreferredSize(new Dimension(180, 200)); } @@ -117,7 +127,18 @@ public class ExplorerPane extends JPanel { public JList> getComponentList() { if (componentList == null) { - componentList = new JList>(); + componentList = new DragDropList>(new IDragDropListListener() { + + @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); @@ -183,8 +204,10 @@ public class ExplorerPane extends JPanel { return popupMenu; } - public void setComponents(List> components) { + public void setComponents(List> components, + Collection> selectedComponents) { this.getComponentListModel().setComponents(components); + setSelection(new HashSet>(selectedComponents)); } public void setSelection(Set> selectedComponents) { @@ -230,7 +253,7 @@ public class ExplorerPane extends JPanel { private List> components; private String searchText; - private Sort sort; + private Sort sort = Sort.Z_INDEX; public void setComponents(List> components) { this.componentsRaw = components; @@ -243,6 +266,10 @@ public class ExplorerPane extends JPanel { return components; } + public Sort getSort() { + return sort; + } + public void setSort(Sort sort) { this.sort = sort; diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPlugin.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPlugin.java index 149dc522..1566d497 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPlugin.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/explorer/ExplorerPlugin.java @@ -64,10 +64,10 @@ public class ExplorerPlugin implements IPlugIn { public void processMessage(EventType eventType, Object... params) { switch (eventType) { case PROJECT_MODIFIED: - getExplorerPane().setComponents(((Project)params[1]).getComponents()); + getExplorerPane().setComponents(((Project)params[1]).getComponents(), plugInPort.getSelectedComponents()); break; case PROJECT_LOADED: - getExplorerPane().setComponents(((Project)params[0]).getComponents()); + getExplorerPane().setComponents(((Project)params[0]).getComponents(), plugInPort.getSelectedComponents()); break; case SELECTION_CHANGED: getExplorerPane().setSelection((Set>)params[0]); @@ -103,7 +103,7 @@ public class ExplorerPlugin implements IPlugIn { public ExplorerPane getExplorerPane() { if (explorerPane == null) { - explorerPane = new ExplorerPane(plugInPort); + explorerPane = new ExplorerPane(swingUI, plugInPort); } return explorerPane; }