Skip to content

Commit

Permalink
Add drag'n'drop to the designer for importing files (#2586)
Browse files Browse the repository at this point in the history
  • Loading branch information
breiler authored Aug 5, 2024
1 parent 62c088e commit 0c55662
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This file is part of Universal Gcode Sender (UGS).
import com.willwinder.ugs.nbp.designer.io.eagle.EaglePnpReader;
import com.willwinder.ugs.nbp.designer.io.kicad.KiCadPosReader;
import com.willwinder.ugs.nbp.designer.io.svg.SvgReader;
import com.willwinder.ugs.nbp.designer.io.ugsd.UgsDesignReader;
import com.willwinder.ugs.nbp.designer.logic.Controller;
import com.willwinder.ugs.nbp.designer.logic.ControllerFactory;
import com.willwinder.ugs.nbp.designer.logic.Tool;
Expand All @@ -41,6 +42,7 @@ This file is part of Universal Gcode Sender (UGS).
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.Arrays;
import java.util.Optional;

/**
Expand All @@ -54,6 +56,14 @@ This file is part of Universal Gcode Sender (UGS).
displayName = "Import file",
lazy = false)
public final class ToolImportAction extends AbstractDesignAction {
public static final FileNameExtensionFilter[] FILE_NAME_EXTENSION_FILTERS = new FileNameExtensionFilter[]{
new FileNameExtensionFilter("Scalable Vector Graphics (.svg)", "svg"),
new FileNameExtensionFilter("Autodesk CAD (.dxf)", "dxf"),
new FileNameExtensionFilter("Carbide Create (.c2d)", "c2d"),
new FileNameExtensionFilter("Eagle (.mnt, .mnb)", "mnt", "mnb"),
new FileNameExtensionFilter("KiCad (.pos)", "pos"),
new FileNameExtensionFilter("UGS design (.ugsd)", "ugsd")
};

public static final String SMALL_ICON_PATH = "img/import.svg";
public static final String LARGE_ICON_PATH = "img/import24.svg";
Expand All @@ -68,51 +78,53 @@ public ToolImportAction() {
this.controller = ControllerFactory.getController();
}

public static void readDesign(Controller controller, BackendAPI backend, File f) {
Optional<Design> optionalDesign = Optional.empty();
if (StringUtils.endsWithIgnoreCase(f.getName(), ".svg")) {
SvgReader svgReader = new SvgReader();
optionalDesign = svgReader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".dxf")) {
DxfReader reader = new DxfReader(backend.getSettings());
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".c2d")) {
C2dReader reader = new C2dReader();
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".mnt") ||
StringUtils.endsWithIgnoreCase(f.getName(), ".mnb")) {
EaglePnpReader reader = new EaglePnpReader();
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".pos")) {
KiCadPosReader reader = new KiCadPosReader();
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".ugsd")) {
UgsDesignReader reader = new UgsDesignReader();
optionalDesign = reader.read(f);
}

if (optionalDesign.isPresent()) {
Design design = optionalDesign.get();
controller.setTool(Tool.SELECT);
controller.addEntities(design.getEntities());
controller.getSelectionManager().addSelection(design.getEntities());
controller.getDrawing().repaint();
} else {
throw new RuntimeException("Could not open: " + f.getName());
}
}

@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fileDialog = new JFileChooser();
fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileDialog.addChoosableFileFilter(new FileNameExtensionFilter("Scalable Vector Graphics (.svg)", "svg"));
fileDialog.addChoosableFileFilter(new FileNameExtensionFilter("Autodesk CAD (.dxf)", "dxf"));
fileDialog.addChoosableFileFilter(new FileNameExtensionFilter("Carbide Create (.c2d)", "c2d"));
fileDialog.addChoosableFileFilter(new FileNameExtensionFilter("Eagle (.mnt, .mnb)", "mnt", "mnb"));
fileDialog.addChoosableFileFilter(new FileNameExtensionFilter("KiCad (.pos)", "pos"));
Arrays.asList(FILE_NAME_EXTENSION_FILTERS).forEach(fileDialog::addChoosableFileFilter);
fileDialog.showOpenDialog(SwingHelpers.getRootFrame());

BackendAPI backend = CentralLookup.getDefault().lookup(BackendAPI.class);

ThreadHelper.invokeLater(() -> {
File f = fileDialog.getSelectedFile();
if (f != null) {

Optional<Design> optionalDesign = Optional.empty();
if (StringUtils.endsWithIgnoreCase(f.getName(), ".svg")) {
SvgReader svgReader = new SvgReader();
optionalDesign = svgReader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".dxf")) {
DxfReader reader = new DxfReader(backend.getSettings());
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".c2d")) {
C2dReader reader = new C2dReader();
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".mnt") ||
StringUtils.endsWithIgnoreCase(f.getName(), ".mnb")) {
EaglePnpReader reader = new EaglePnpReader();
optionalDesign = reader.read(f);
} else if (StringUtils.endsWithIgnoreCase(f.getName(), ".pos")) {
KiCadPosReader reader = new KiCadPosReader();
optionalDesign = reader.read(f);
}

if (optionalDesign.isPresent()) {
Design design = optionalDesign.get();
controller.setTool(Tool.SELECT);
controller.addEntities(design.getEntities());
controller.getSelectionManager().addSelection(design.getEntities());
controller.getDrawing().repaint();
} else {
throw new RuntimeException("Could not open: " + f.getName());
}
readDesign(controller, backend, f);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ This file is part of Universal Gcode Sender (UGS).
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
Expand Down Expand Up @@ -77,6 +79,8 @@ public class Drawing extends JPanel {
private double scale;
private Point2D.Double position = new Point2D.Double();
private Dimension oldMinimumSize;
private transient DropHandler dropHandler;
private transient DropTarget dropTarget;

public Drawing(Controller controller) {
refreshThrottler = new Throttler(this::refresh, 1000);
Expand Down Expand Up @@ -115,6 +119,19 @@ public Drawing(Controller controller) {
setScale(2);
}

@Override
public void addNotify() {
super.addNotify();
dropHandler = new DropHandler();
dropTarget = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, dropHandler, true);
}

@Override
public void removeNotify() {
super.removeNotify();
dropTarget.removeDropTargetListener(dropHandler);
}

public BufferedImage getImage() {
BufferedImage bi = new BufferedImage(getPreferredSize().width,
getPreferredSize().height, BufferedImage.TYPE_INT_RGB);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
Copyright 2024 Will Winder
This file is part of Universal Gcode Sender (UGS).
UGS 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.
UGS 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 UGS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.willwinder.ugs.nbp.designer.gui;

import com.willwinder.ugs.nbp.designer.actions.ToolImportAction;
import static com.willwinder.ugs.nbp.designer.actions.ToolImportAction.FILE_NAME_EXTENSION_FILTERS;
import com.willwinder.ugs.nbp.designer.logic.ControllerFactory;
import com.willwinder.ugs.nbp.lib.lookup.CentralLookup;
import com.willwinder.universalgcodesender.model.BackendAPI;
import com.willwinder.universalgcodesender.utils.ThreadHelper;
import org.apache.commons.lang3.StringUtils;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;

/**
* Listens for drag'n'drop of known file types and imports it via the {@link ToolImportAction}.
*
* @author Joacim Breiler
*/
public class DropHandler implements DropTargetListener {
private static Optional<String> getFilename(DataFlavor stringFlavor, Transferable transferable) {
try {
String filename = (String) transferable.getTransferData(stringFlavor);
if (hasCorrectExtension(filename)) {
return Optional.of(filename);
}
} catch (UnsupportedFlavorException | IOException e) {
// Never mind...
}

return Optional.empty();
}

private static boolean hasCorrectExtension(String filename) {
return Arrays.stream(FILE_NAME_EXTENSION_FILTERS)
.flatMap(s -> Arrays.stream(s.getExtensions()))
.anyMatch(extension -> StringUtils.endsWithIgnoreCase(filename, extension));
}

private Optional<String> getFilename(DropTargetDragEvent dtde) {
DataFlavor stringFlavor = DataFlavor.stringFlavor;
if (!dtde.isDataFlavorSupported(stringFlavor)) {
return Optional.empty();
}

Transferable transferable = dtde.getTransferable();
return getFilename(stringFlavor, transferable);
}

private Optional<String> getFilename(DropTargetDropEvent dtde) {
DataFlavor stringFlavor = DataFlavor.stringFlavor;
if (!dtde.isDataFlavorSupported(stringFlavor)) {
return Optional.empty();
}

dtde.acceptDrop(DnDConstants.ACTION_COPY);
Transferable transferable = dtde.getTransferable();
return getFilename(stringFlavor, transferable);
}

@Override
public void dragEnter(DropTargetDragEvent dtde) {
Optional<String> filename = getFilename(dtde);
if (filename.isEmpty()) {
dtde.rejectDrag();
return;
}

dtde.acceptDrag(DnDConstants.ACTION_COPY);
}

@Override
public void dragOver(DropTargetDragEvent dtde) {
dragEnter(dtde);
}

@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
// Not used
}

@Override
public void dragExit(DropTargetEvent dte) {
// Not used
}

@Override
public void drop(DropTargetDropEvent dtde) {
Optional<String> filename = getFilename(dtde);
if (filename.isEmpty()) {
dtde.dropComplete(false);
return;
}

ThreadHelper.invokeLater(() -> {
File file = filename.map(File::new).get();
ToolImportAction.readDesign(ControllerFactory.getController(), CentralLookup.getDefault().lookup(BackendAPI.class), file);
dtde.dropComplete(true);
});
}
}

0 comments on commit 0c55662

Please sign in to comment.