/** * This file is part of VisiCut. * Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de> * RWTH Aachen University - 52062 Aachen, Germany * * VisiCut is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VisiCut 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with VisiCut. If not, see <http://www.gnu.org/licenses/>. **/ package com.t_oster.visicut; import com.t_oster.liblasercut.IllegalJobException; import com.t_oster.liblasercut.LaserCutter; import com.t_oster.liblasercut.LaserJob; import com.t_oster.liblasercut.LaserProperty; import com.t_oster.liblasercut.ProgressListener; import com.t_oster.visicut.managers.LaserDeviceManager; import com.t_oster.visicut.managers.MappingManager; import com.t_oster.visicut.managers.MaterialManager; import com.t_oster.visicut.managers.PreferencesManager; import com.t_oster.visicut.misc.ExtensionFilter; import com.t_oster.visicut.misc.FileUtils; import com.t_oster.visicut.misc.Helper; import com.t_oster.visicut.misc.MultiFilter; import com.t_oster.visicut.model.LaserDevice; import com.t_oster.visicut.model.LaserProfile; import com.t_oster.visicut.model.MaterialProfile; import com.t_oster.visicut.model.PlfFile; import com.t_oster.visicut.model.PlfPart; import com.t_oster.visicut.model.graphicelements.GraphicFileImporter; import com.t_oster.visicut.model.graphicelements.GraphicSet; import com.t_oster.visicut.model.graphicelements.ImportException; import com.t_oster.visicut.model.mapping.Mapping; import com.t_oster.visicut.model.mapping.MappingSet; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.beans.Encoder; import java.beans.Expression; import java.beans.PersistenceDelegate; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import javax.imageio.ImageIO; import javax.swing.filechooser.FileFilter; /** * This class contains the state and business logic of the * Application * * @author Thomas Oster <thomas.oster@rwth-aachen.de> */ public class VisicutModel { public void addScreenshotOfBackgroundImage(Rectangle crop, Rectangle2D target) throws IOException, FileNotFoundException, ImportException { File tmp = FileUtils.getNonexistingWritableFile("screenshot.png"); tmp.deleteOnExit(); BufferedImage cut = this.backgroundImage.getSubimage(crop.x, crop.y, crop.width, crop.height); ImageIO.write(cut, "png", tmp); PlfPart p = this.loadGraphicFile(tmp, new LinkedList<String>()); if (target != null) { p.getGraphicObjects().setTransform(Helper.getTransform(p.getGraphicObjects().getOriginalBoundingBox(), target)); } this.plfFile.add(p); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_ADDED, null, p); this.setSelectedPart(p); } /** * Duplicates the given PlfPart and adds it to the * current PlfFile * @param p */ public void duplicate(PlfPart p) { PlfPart dup = new PlfPart(); dup.setSourceFile(p.getSourceFile() != null ? p.getSourceFile() : null); dup.setMapping(p.getMapping() != null ? p.getMapping().clone() : null); if (p.getGraphicObjects() != null) { dup.setGraphicObjects(p.getGraphicObjects().clone()); } this.plfFile.add(dup); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_ADDED, null, dup); } private Point2D.Double startPoint = null; public static final String PROP_STARTPOINT = "startPoint"; public Point2D.Double getStartPoint() { return startPoint; } public void setStartPoint(Point2D.Double startPoint) { Point2D.Double oldStartPoint = this.startPoint; this.startPoint = startPoint; propertyChangeSupport.firePropertyChange(PROP_STARTPOINT, oldStartPoint, startPoint); } private PlfPart selectedPart = null; public static final String PROP_SELECTEDPART = "selectedPart"; /** * Get the value of selectedPart * * @return the value of selectedPart */ public PlfPart getSelectedPart() { return selectedPart; } public FileFilter getAllFileFilter() { List<FileFilter> filters = new LinkedList<FileFilter>(); filters.add(PLFFilter); filters.addAll(Arrays.asList(this.getGraphicFileImporter().getFileFilters())); return new MultiFilter(filters); } /** * Set the value of selectedPart * * @param selectedPart new value of selectedPart */ public void setSelectedPart(PlfPart selectedPart) { PlfPart oldSelectedPart = this.selectedPart; this.selectedPart = selectedPart; if (selectedPart == null && this.plfFile != null && !this.plfFile.isEmpty()) { this.selectedPart = this.plfFile.get(0); } propertyChangeSupport.firePropertyChange(PROP_SELECTEDPART, oldSelectedPart, selectedPart); } private PlfFile plfFile = new PlfFile(); /** * Get the value of plfFile * * @return the value of plfFile */ public PlfFile getPlfFile() { return plfFile; } protected float materialThickness = 0; public static final String PROP_MATERIALTHICKNESS = "materialThickness"; /** * Get the value of materialThickness * * @return the value of materialThickness */ public float getMaterialThickness() { return materialThickness; } /** * Set the value of materialThickness * * @param materialThickness new value of materialThickness */ public void setMaterialThickness(float materialThickness) { float oldMaterialThickness = this.materialThickness; this.materialThickness = materialThickness; propertyChangeSupport.firePropertyChange(PROP_MATERIALTHICKNESS, oldMaterialThickness, materialThickness); } protected boolean useThicknessAsFocusOffset = true; public static final String PROP_USETHICKNESSASFOCUSOFFSET = "useThicknessAsFocusOffset"; /** * Get the value of useThicknessAsFocusOffset * * @return the value of useThicknessAsFocusOffset */ public boolean isUseThicknessAsFocusOffset() { return useThicknessAsFocusOffset; } /** * Set the value of useThicknessAsFocusOffset * * @param useThicknessAsFocusOffset new value of useThicknessAsFocusOffset */ public void setUseThicknessAsFocusOffset(boolean useThicknessAsFocusOffset) { boolean oldUseThicknessAsFocusOffset = this.useThicknessAsFocusOffset; this.useThicknessAsFocusOffset = useThicknessAsFocusOffset; propertyChangeSupport.firePropertyChange(PROP_USETHICKNESSASFOCUSOFFSET, oldUseThicknessAsFocusOffset, useThicknessAsFocusOffset); } public static final FileFilter PLFFilter = new ExtensionFilter(".plf", "VisiCut Portable Laser Format (*.plf)"); private static VisicutModel instance; public static VisicutModel getInstance() { if (instance == null){ instance = new VisicutModel(); } return instance; } /** * This Constructor is only for the UI Editor to run properly * Do not use. use getInstance() instead. */ public VisicutModel() { if (instance != null) { System.err.println("Should not use public Constructor of VisicutModel"); } } protected LaserDevice selectedLaserDevice = null; public static final String PROP_SELECTEDLASERDEVICE = "selectedLaserDevice"; /** * Get the value of selectedLaserDevice * * @return the value of selectedLaserDevice */ public LaserDevice getSelectedLaserDevice() { return selectedLaserDevice; } /** * Set the value of selectedLaserDevice * * @param selectedLaserDevice new value of selectedLaserDevice */ public void setSelectedLaserDevice(LaserDevice selectedLaserDevice) { LaserDevice oldSelectedLaserDevice = this.selectedLaserDevice; this.selectedLaserDevice = selectedLaserDevice; propertyChangeSupport.firePropertyChange(PROP_SELECTEDLASERDEVICE, oldSelectedLaserDevice, selectedLaserDevice); } protected BufferedImage backgroundImage = null; public static final String PROP_BACKGROUNDIMAGE = "backgroundImage"; /** * Get the value of backgroundImage * * @return the value of backgroundImage */ public BufferedImage getBackgroundImage() { return backgroundImage; } /** * Set the value of backgroundImage * * @param backgroundImage new value of backgroundImage */ public void setBackgroundImage(BufferedImage backgroundImage) { BufferedImage oldBackgroundImage = this.backgroundImage; this.backgroundImage = backgroundImage; propertyChangeSupport.firePropertyChange(PROP_BACKGROUNDIMAGE, oldBackgroundImage, backgroundImage); } protected Preferences preferences = new Preferences(); public static final String PROP_PREFERENCES = "preferences"; /** * Get the value of preferences * * @return the value of preferences */ public Preferences getPreferences() { return preferences; } /** * Set the value of preferences * * @param preferences new value of preferences */ public void setPreferences(Preferences preferences) { Preferences oldPreferences = this.preferences; this.preferences = preferences; propertyChangeSupport.firePropertyChange(PROP_PREFERENCES, oldPreferences, preferences); if (this.preferences != null) { this.graphicFileImporter = null; List<LaserDevice> devices = LaserDeviceManager.getInstance().getAll(); if (this.preferences.lastLaserDevice != null) { for (LaserDevice ld : devices) { if (this.preferences.lastLaserDevice.equals(ld.getName())) { this.setSelectedLaserDevice(ld); break; } } } else if (!devices.isEmpty()) {//select first laser-device by default this.setSelectedLaserDevice(devices.get(0)); } List<MaterialProfile> materials = MaterialManager.getInstance().getAll(); if (this.preferences.lastMaterial != null) { for (MaterialProfile mp : materials) { if (this.preferences.lastMaterial.equals(mp.getName())) { this.setMaterial(mp); break; } } } else if (!materials.isEmpty()) {//use first material by default this.setMaterial(materials.get(0)); } this.setUseThicknessAsFocusOffset(this.preferences.isUseThicknessAsFocusOffset()); } } public void updatePreferences() { this.preferences.setLastLaserDevice(this.selectedLaserDevice == null ? null : selectedLaserDevice.getName()); this.preferences.setLastMaterial(this.material == null ? null : material.getName()); this.preferences.setUseThicknessAsFocusOffset(this.useThicknessAsFocusOffset); try { PreferencesManager.getInstance().savePreferences(); } catch (Exception ex) { Logger.getLogger(VisicutModel.class.getName()).log(Level.SEVERE, null, ex); } } private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * Add PropertyChangeListener. * * @param listener */ public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } /** * Remove PropertyChangeListener. * * @param listener */ public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void loadFile(MappingManager mm, File file, List<String> warnings, boolean discardCurrent) throws FileNotFoundException, IOException, ImportException { if (PLFFilter.accept(file)) { PlfFile newFile = loadPlfFile(mm, file, warnings); if (newFile != null) { if (discardCurrent) { this.setPlfFile(newFile); } else { if (!newFile.isEmpty()) { for (PlfPart p : newFile) { this.plfFile.add(p); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_ADDED, null, p); } this.setSelectedPart(newFile.get(newFile.size() - 1)); } } } } else { if (discardCurrent) { PlfFile nf = new PlfFile(); nf.setFile(new File(file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")) +".plf")); this.setPlfFile(nf); } PlfPart p = loadGraphicFile(file, warnings); this.plfFile.add(p); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_ADDED, null, p); this.setSelectedPart(p); } } private PlfFile loadPlfFile(MappingManager mm, File f, List<String> warnings) throws FileNotFoundException, IOException, ImportException { ZipFile zip = new ZipFile(f); PlfFile resultingFile = new PlfFile(); resultingFile.setFile(f); Map<Integer,PlfPart> result = new LinkedHashMap<Integer,PlfPart>(); Map<Integer,AffineTransform> transforms = new LinkedHashMap<Integer,AffineTransform>(); Enumeration entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); String name = entry.getName(); Integer i = name.matches("[0-9]+/.*") ? Integer.parseInt(name.split("/")[0]) : 0; if (!result.containsKey(i)) { result.put(i, new PlfPart()); } if (name.equals((i > 0 ? i+"/" : "")+"transform.xml")) { XMLDecoder decoder = new XMLDecoder(zip.getInputStream(entry)); transforms.put(i, (AffineTransform) decoder.readObject()); } else if (name.equals((i > 0 ? i+"/" : "")+"mappings.xml")) { MappingSet map = mm.loadFromFile(zip.getInputStream(entry)); if (map != null) { result.get(i).setMapping(map); } else { warnings.add("Could not load Mapping "+i+" from PLF File"); } } else { int k = 0; result.get(i).setSourceFile(FileUtils.getNonexistingWritableFile(name.replace("/","_"))); byte[] buf = new byte[1024]; InputStream in = zip.getInputStream(entry); FileOutputStream out = new FileOutputStream(result.get(i).getSourceFile()); // Transfer bytes from the file to the ZIP file int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } out.close(); in.close(); result.get(i).getSourceFile().deleteOnExit(); } } for (Integer i : result.keySet()) { result.get(i).setGraphicObjects(this.loadSetFromFile(result.get(i).getSourceFile(), warnings)); if (result.get(i).getGraphicObjects() == null) { warnings.add("Corrupted input file "+i); } else { resultingFile.add(result.get(i)); } if (transforms.containsKey(i)) { result.get(i).getGraphicObjects().setTransform(transforms.get(i)); } else { warnings.add("Could not load Transform "+i+" from PLF File"); } } return resultingFile; } public void saveToFile(MaterialManager pm, MappingManager mm, File f) throws FileNotFoundException, IOException { PlfFile plf = this.getPlfFile(); FileInputStream in; byte[] buf = new byte[1024]; int len; // Create the ZIP file ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f)); //find temporary file for xml int k = 0; File tmp = null; do { tmp = new File(Helper.getBasePath(), "tmp" + (k++) + ".xml"); } while (tmp.exists()); for(int i = 0; i < plf.size(); i++) { // Add source GraphicsFile to the Zip File out.putNextEntry(new ZipEntry((i > 0 ? i+"/" : "")+plf.get(i).getSourceFile().getName())); in = new FileInputStream(plf.get(i).getSourceFile()); // Transfer bytes from the file to the ZIP file while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); // Complete the entry out.closeEntry(); AffineTransform at = plf.get(i).getGraphicObjects().getTransform(); if (at != null) { out.putNextEntry(new ZipEntry((i > 0 ? i+"/" : "")+"transform.xml")); //Write xml to temp file //TODO: Why not directly write to zip output stream? XMLEncoder encoder = new XMLEncoder(new FileOutputStream(tmp)); encoder.setPersistenceDelegate(AffineTransform.class, new PersistenceDelegate() {//Fix for older java versions protected Expression instantiate(Object oldInstance, Encoder out) { AffineTransform tx = (AffineTransform) oldInstance; double[] coeffs = new double[6]; tx.getMatrix(coeffs); return new Expression(oldInstance, oldInstance.getClass(), "new", new Object[] { coeffs }); } }); encoder.writeObject(at); encoder.close(); in = new FileInputStream(tmp); // Transfer bytes from the file to the ZIP file while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.closeEntry(); } if (plf.get(i).getMapping() != null) { out.putNextEntry(new ZipEntry((i > 0 ? i+"/" : "")+"mappings.xml")); mm.save(plf.get(i).getMapping(), tmp); in = new FileInputStream(tmp); // Transfer bytes from the file to the ZIP file while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.closeEntry(); } } // Complete the ZIP file out.close(); // Delete the tmp file tmp.delete(); } private GraphicFileImporter graphicFileImporter = null; public GraphicFileImporter getGraphicFileImporter() { if (graphicFileImporter == null) { graphicFileImporter = new GraphicFileImporter(this.preferences.getAvailableImporters()); } return graphicFileImporter; } private GraphicSet loadSetFromFile(File f, List<String> warnings) throws ImportException { GraphicFileImporter im = this.getGraphicFileImporter(); GraphicSet set = im.importFile(f, warnings); return set; } private PlfPart loadGraphicFile(File f, List<String> warnings) throws ImportException { GraphicSet gs = this.loadSetFromFile(f, warnings); PlfPart p = null; if (gs != null) { p = new PlfPart(); p.setGraphicObjects(gs); p.setSourceFile(f); } return p; } protected MaterialProfile material = null; public static final String PROP_MATERIAL = "material"; /** * Get the value of material * * @return the value of material */ public MaterialProfile getMaterial() { return material; } /** * Set the value of material * * @param material new value of material */ public void setMaterial(MaterialProfile material) { MaterialProfile oldMaterial = this.material; this.material = material; propertyChangeSupport.firePropertyChange(PROP_MATERIAL, oldMaterial, material); } private LaserJob prepareJob(String name, Map<LaserProfile, List<LaserProperty>> propmap) throws FileNotFoundException, IOException { LaserJob job = new LaserJob(name, name, "visicut"); if (this.startPoint != null) { job.setStartPoint(this.startPoint.x, this.startPoint.y); } float focusOffset = this.selectedLaserDevice.getLaserCutter().isAutoFocus() || !this.useThicknessAsFocusOffset ? 0 : this.materialThickness; for (PlfPart p : this.getPlfFile()) { if (p.getMapping() == null) { continue; } for (Mapping m : p.getMapping()) { GraphicSet set = m.getFilterSet() != null ? m.getFilterSet().getMatchingObjects(p.getGraphicObjects()) : p.getUnmatchedObjects(); LaserProfile pr = m.getProfile(); if (pr == null)//ignore-profile { continue; } List<LaserProperty> props = propmap.get(pr); pr.addToLaserJob(job, set, this.addFocusOffset(props, focusOffset)); } } return job; } public void sendJob(String name, ProgressListener pl, Map<LaserProfile, List<LaserProperty>> props, List<String> warnings) throws IllegalJobException, SocketTimeoutException, Exception { LaserCutter lasercutter = this.getSelectedLaserDevice().getLaserCutter(); if (pl != null) { pl.taskChanged(this, "preparing job"); } LaserJob job = this.prepareJob(name, props); if (pl != null) { pl.taskChanged(this, "sending job"); lasercutter.sendJob(job, pl, warnings); } else { lasercutter.sendJob(job, warnings); } } public int estimateTime(Map<LaserProfile, List<LaserProperty>> propmap) throws FileNotFoundException, IOException { LaserCutter lc = this.getSelectedLaserDevice().getLaserCutter(); LaserJob job = this.prepareJob("calc", propmap); return lc.estimateJobDuration(job); } private List<LaserProperty> addFocusOffset(List<LaserProperty> props, float focusOffset) { if (focusOffset == 0 || props.isEmpty() || props.get(0).getProperty("focus") == null) { return props; } List<LaserProperty> result = new LinkedList<LaserProperty>(); for(LaserProperty p:props) { LaserProperty c = p.clone(); Object foc = c.getProperty("focus"); if (foc instanceof Integer) { c.setProperty("focus", (Integer) (((Integer) foc)+ (int) focusOffset)); } else if (foc instanceof Float) { c.setProperty("focus", (Float) foc + focusOffset); } else if (foc instanceof Double) { c.setProperty("focus", (Double) (((Double) foc)+ (double) focusOffset)); } result.add(c); } return result; } public enum Modification { NONE, RESIZE, MOVE, ROTATE } /** * Adjusts the Transform of the Graphic-Objects such that the Objects * fit into the current Laser-Bed. * If modification was necessary, it returns true. * @return */ public Modification fitObjectsIntoBed() { //TODO rotate file 90° if it would fit then Modification result = Modification.NONE; double bw = getSelectedLaserDevice() != null ? getSelectedLaserDevice().getLaserCutter().getBedWidth() : 600d; double bh = getSelectedLaserDevice() != null ? getSelectedLaserDevice().getLaserCutter().getBedHeight() : 300d; for(PlfPart p : this.plfFile) { boolean modified = false; Rectangle2D bb = p.getGraphicObjects().getBoundingBox(); AffineTransform trans = p.getGraphicObjects().getTransform(); //first try moving to origin, if not in range if (bb.getX() < 0 || bb.getX() + bb.getWidth() > bw) { trans.preConcatenate(AffineTransform.getTranslateInstance(-bb.getX(), 0)); p.getGraphicObjects().setTransform(trans); bb = p.getGraphicObjects().getBoundingBox(); modified = true; result = Modification.MOVE; } if (bb.getY() < 0 || bb.getY() + bb.getHeight() > bh) { trans.preConcatenate(AffineTransform.getTranslateInstance(0, -bb.getY())); p.getGraphicObjects().setTransform(trans); bb = p.getGraphicObjects().getBoundingBox(); modified = true; result = Modification.MOVE; } //if still too big (we're in origin now) check if rotation is useful if (bb.getX() + bb.getWidth() > bw || bb.getY() + bb.getHeight() > bh) { //check if laser-bed and graphic are not in the same orientation (landscape vs portrait) if ((bw / bh >= 1) != (bb.getWidth() / bb.getHeight() >= 1)) {//if so, rotate the graphic 90 degrees, keeping the x and y value double oldX = bb.getX(); double oldY = bb.getY(); trans.preConcatenate(AffineTransform.getQuadrantRotateInstance(3)); p.getGraphicObjects().setTransform(trans); bb = p.getGraphicObjects().getBoundingBox(); //move to old position trans.preConcatenate(AffineTransform.getTranslateInstance(oldX-bb.getX(), oldY-bb.getY())); p.getGraphicObjects().setTransform(trans); bb = p.getGraphicObjects().getBoundingBox(); result = Modification.ROTATE; } } //Do not scale the object, because this might be very confising for the user //if still too big (we're in origin now) we have to resize, but keeping if (bb.getX() + bb.getWidth() > bw || bb.getY() + bb.getHeight() > bh) { double factor = Math.min(bw/bb.getWidth(), bh/bb.getHeight()); trans.preConcatenate(AffineTransform.getScaleInstance(factor, factor)); p.getGraphicObjects().setTransform(trans); modified = true; result = Modification.RESIZE; } if (modified) { this.firePartUpdated(p); } } return result; } public static final String PROP_PLF_FILE_CHANGED = "plf file changed"; public static final String PROP_PLF_PART_ADDED = "plf part added"; public static final String PROP_PLF_PART_REMOVED = "plf part removed"; public static final String PROP_PLF_PART_UPDATED = "plf part updated"; private void setPlfFile(PlfFile resultingFile) { this.plfFile = resultingFile; this.setSelectedPart(null); this.propertyChangeSupport.firePropertyChange(PROP_PLF_FILE_CHANGED, null, plfFile); } public void newPlfFile() { this.setPlfFile(new PlfFile()); } public void reloadSelectedPart(LinkedList<String> warnings) throws ImportException { if (this.selectedPart != null) { AffineTransform tr = this.selectedPart.getGraphicObjects().getTransform(); this.selectedPart.setGraphicObjects(this.loadSetFromFile(this.selectedPart.getSourceFile(), warnings)); this.selectedPart.getGraphicObjects().setTransform(tr); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_UPDATED, null, this.selectedPart); } } public void removePlfPart(PlfPart p) { if (p.equals(this.selectedPart)) { this.setSelectedPart(null); } this.plfFile.remove(p); this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_REMOVED, p, null); } public void removeSelectedPart() { if (this.selectedPart != null) { this.removePlfPart(this.selectedPart); } this.setSelectedPart(this.plfFile != null && !this.plfFile.isEmpty() ? this.plfFile.get(0) : null); } public void firePartUpdated(PlfPart p) { this.propertyChangeSupport.firePropertyChange(PROP_PLF_PART_UPDATED, null, p); } }