org.pepsoft.worldpainter.CustomLayerController Maven / Gradle / Ivy
package org.pepsoft.worldpainter;
import com.google.common.collect.Lists;
import com.jidesoft.docking.DockContext;
import com.jidesoft.docking.DockableFrame;
import org.jetbrains.annotations.NotNull;
import org.pepsoft.minecraft.Material;
import org.pepsoft.util.DesktopUtils;
import org.pepsoft.util.IconUtils;
import org.pepsoft.util.swing.BetterJPopupMenu;
import org.pepsoft.worldpainter.importing.CustomItemsTreeModel;
import org.pepsoft.worldpainter.layers.*;
import org.pepsoft.worldpainter.layers.annotation.CustomAnnotationLayerDialog;
import org.pepsoft.worldpainter.layers.groundcover.GroundCoverLayer;
import org.pepsoft.worldpainter.layers.plants.PlantLayer;
import org.pepsoft.worldpainter.layers.pockets.UndergroundPocketsDialog;
import org.pepsoft.worldpainter.layers.pockets.UndergroundPocketsLayer;
import org.pepsoft.worldpainter.layers.tunnel.TunnelLayer;
import org.pepsoft.worldpainter.layers.tunnel.TunnelLayerDialog;
import org.pepsoft.worldpainter.operations.PaintOperation;
import org.pepsoft.worldpainter.painting.LayerPaint;
import org.pepsoft.worldpainter.palettes.Palette;
import org.pepsoft.worldpainter.palettes.PaletteManager;
import org.pepsoft.worldpainter.plugins.CustomLayerProvider;
import org.pepsoft.worldpainter.plugins.WPPluginManager;
import org.pepsoft.worldpainter.util.FileFilter;
import org.pepsoft.worldpainter.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.text.MessageFormat;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import java.util.zip.GZIPOutputStream;
import static java.awt.Color.BLACK;
import static java.awt.Color.YELLOW;
import static java.util.Arrays.asList;
import static java.util.Comparator.comparing;
import static javax.swing.JOptionPane.*;
import static org.pepsoft.minecraft.Constants.DEFAULT_WATER_LEVEL;
import static org.pepsoft.util.AwtUtils.doLaterOnEventThread;
import static org.pepsoft.util.swing.MessageUtils.*;
import static org.pepsoft.worldpainter.App.COMMAND_KEY_NAME;
import static org.pepsoft.worldpainter.Constants.TILE_SIZE;
import static org.pepsoft.worldpainter.Constants.TILE_SIZE_BITS;
import static org.pepsoft.worldpainter.Dimension.Role.CAVE_FLOOR;
public class CustomLayerController implements PropertyChangeListener {
CustomLayerController(App app) {
this.app = app;
}
// PropertyChangeListener
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ((evt.getSource() instanceof Palette) && (evt.getPropertyName().equals("show") || evt.getPropertyName().equals("solo"))) {
if (evt.getPropertyName().equals("solo") && evt.getNewValue() == Boolean.TRUE) {
for (Palette palette: paletteManager.getPalettes()) {
if ((palette != evt.getSource()) && palette.isSolo()) {
palette.setSolo(false);
}
}
}
app.updateLayerVisibility();
}
}
public List createCustomLayerButton(final CustomLayer layer) {
final List buttonComponents = app.createLayerButton(layer, '\0');
final JToggleButton button = (JToggleButton) buttonComponents.get(2);
button.setToolTipText(button.getToolTipText() + "; right-click for options");
button.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
}
}
private void showPopup(MouseEvent e) {
final JPopupMenu popup = new BetterJPopupMenu();
JMenuItem menuItem = new JMenuItem(strings.getString("edit") + "...");
menuItem.addActionListener(e1 -> editCustomLayer(layer));
popup.add(menuItem);
final Dimension dimension = app.getDimension();
if ((layer instanceof TunnelLayer)) {
final TunnelLayer tunnelLayer = (TunnelLayer) layer;
final Integer floorDimensionId = tunnelLayer.getFloorDimensionId();
if (floorDimensionId != null) {
menuItem = new JMenuItem("Edit floor dimension");
if (dimension.containsOneOf(layer)) {
menuItem.addActionListener(e1 -> {
final Point viewPosition = app.view.getViewCentreInWorldCoords();
final Dimension floorDimension = tunnelLayer.updateFloorDimension(dimension, null);
app.setDimension(floorDimension);
// Initially we move to the same location as we were on the surface. Then we check
// whether the floor dimension is actually visible then. If not, we try to find the
// middle and move there
// TODO: this might not be helpful if the layer is painted in multiple discontiguous areas
app.view.moveTo(viewPosition);
int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE;
final Rectangle visibleArea = app.view.getVisibleArea();
boolean floorTileVisible = false;
for (Tile tile: floorDimension.getTiles()) {
// Check if the tile is entirely visible. If so, we're done and will
if (visibleArea.contains(tile.getX() << TILE_SIZE_BITS, tile.getY() << TILE_SIZE_BITS)
&& visibleArea.contains((tile.getX() << TILE_SIZE_BITS) + TILE_SIZE - 1, (tile.getY() << TILE_SIZE_BITS) + TILE_SIZE - 1)) {
floorTileVisible = true;
break;
}
// Record the extents of the tiles of the floor dimension
if (tile.getX() < minX) {
minX = tile.getX();
}
if (tile.getX() > maxX) {
maxX = tile.getX();
}
if (tile.getY() < minY) {
minY = tile.getY();
}
if (tile.getY() > maxY) {
maxY = tile.getY();
}
};
if ((! floorTileVisible) && (minX != Integer.MAX_VALUE)) {
app.view.moveTo((((maxX + minX) / 2) << TILE_SIZE_BITS) + (TILE_SIZE / 2),
(((maxY + minY) / 2) << TILE_SIZE_BITS) + (TILE_SIZE / 2));
}
final Configuration config = Configuration.getInstance();
if (! config.isMessageDisplayedCountAtLeast(EDITING_FLOOR_DIMENSION_KEY, 3)) {
doLaterOnEventThread(() -> JOptionPane.showMessageDialog(app,
"Press Esc to finish editing the Custom Cave/Tunnel layer floor dimension,\n" +
"or select the Surface dimension from the app.view menu or by pressing " + COMMAND_KEY_NAME + "+U", "Editing Cave/Tunnel Floor", JOptionPane.INFORMATION_MESSAGE));
config.setMessageDisplayed(EDITING_FLOOR_DIMENSION_KEY);
}
final JLabel label = new JLabel("Press Esc to leave the Custom Cave/Tunnel Floor Dimension.");
label.setBorder(new CompoundBorder(new LineBorder(BLACK), new EmptyBorder(5, 5, 5, 5)));
app.pushGlassPaneComponent(label);
});
} else {
menuItem.setEnabled(false);
}
popup.add(menuItem);
}
}
menuItem = new JMenuItem("Duplicate...");
if (layer.isExportableToFile()) {
menuItem.addActionListener(e1 -> duplicate());
} else {
menuItem.setEnabled(false);
menuItem.setToolTipText("This layer cannot be duplicated.");
}
popup.add(menuItem);
menuItem = new JMenuItem(strings.getString("remove") + "...");
menuItem.addActionListener(e1 -> remove());
popup.add(menuItem);
menuItem = new JMenuItem("Export to file...");
if (layer.isExportableToFile()) {
menuItem.addActionListener(e1 -> exportLayer(layer));
} else {
menuItem.setEnabled(false);
menuItem.setToolTipText("This layer cannot be exported to a file.");
}
popup.add(menuItem);
JMenu paletteMenu = new JMenu("Move to palette");
for (final Palette palette: paletteManager.getPalettes()) {
menuItem = new JMenuItem(palette.getName());
menuItem.addActionListener(e1 -> moveLayerToPalette(layer, palette));
if (palette.contains(layer)) {
menuItem.setEnabled(false);
}
paletteMenu.add(menuItem);
}
menuItem = new JMenuItem("New palette...");
menuItem.addActionListener(e1 -> createNewLayerPalette(layer));
paletteMenu.add(menuItem);
popup.add(paletteMenu);
List actions = layer.getActions();
if (actions != null) {
for (Action action : actions) {
action.putValue(CustomLayer.KEY_DIMENSION, dimension);
popup.add(new JMenuItem(action));
}
}
popup.show(button, e.getX(), e.getY());
}
private void duplicate() {
final CustomLayer duplicate = layer.clone();
duplicate.setName("Copy of " + layer.getName());
final Object paint = layer.getPaint();
if (paint instanceof Color) {
Color colour = (Color) paint;
final float[] hsb = Color.RGBtoHSB(colour.getRed(), colour.getGreen(), colour.getBlue(), null);
hsb[0] += 1f / 12;
if (hsb[0] > 1f) {
hsb[0] -= 1f;
}
colour = Color.getHSBColor(hsb[0], hsb[1], hsb[2]);
duplicate.setPaint(colour);
}
AbstractEditLayerDialog dialog;
dialog = createEditLayerDialog(duplicate);
dialog.setVisible(() -> registerCustomLayer(dialog.getLayer(), true));
}
private void remove() {
if (showConfirmDialog(app, MessageFormat.format(strings.getString("are.you.sure.you.want.to.remove.the.0.layer"), layer.getName()), MessageFormat.format(strings.getString("confirm.0.removal"), layer.getName()), YES_NO_OPTION) == YES_OPTION) {
deleteCustomLayer(layer);
app.validate(); // Doesn't happen automatically for some reason; Swing bug?
}
}
});
return buttonComponents;
}
public void layerRemoved(CustomLayer layer) {
app.layerRemoved(layer);
}
public List createPopupMenuButton() {
final JButton addLayerButton = new JButton(ADD_CUSTOM_LAYER_BUTTON_ICON);
addLayerButton.setToolTipText(strings.getString("add.a.custom.layer"));
addLayerButton.setMargin(new Insets(2, 2, 2, 2));
addLayerButton.addActionListener(e -> {
if (app.getDimension() == null) {
DesktopUtils.beep();
return;
}
// Find out which palette the button is on
Container parent = addLayerButton.getParent();
while ((parent != null) && (! (parent instanceof DockableFrame))) {
parent = parent.getParent();
}
if (parent != null) {
final String nameKey = ((DockableFrame) parent).getKey();
final String paletteName = nameKey.substring(nameKey.indexOf('.') + 1);
final JPopupMenu customLayerMenu = createCustomLayerMenu(paletteName);
customLayerMenu.show(addLayerButton, addLayerButton.getWidth(), 0);
} else {
logger.error("Could not find palette add layer button is on");
DesktopUtils.beep();
}
});
final List addLayerButtonPanel = new ArrayList<>(3);
addLayerButtonPanel.add(new JPanel());
addLayerButtonPanel.add(new JPanel());
addLayerButtonPanel.add(addLayerButton);
return addLayerButtonPanel;
}
void registerCustomLayer(final CustomLayer layer, boolean activate) {
// Add to palette, creating it if necessary
Palette palette = paletteManager.register(layer);
// Show the palette if it is not showing yet
if (palette != null) {
app.dockingManager.addFrame(palette.getDockableFrame());
app.dockingManager.dockFrame(palette.getDockableFrame().getKey(), DockContext.DOCK_SIDE_WEST, 3);
if (activate) {
app.dockingManager.activateFrame(palette.getDockableFrame().getKey());
}
palette.addPropertyChangeListener(this);
} else {
app.validate();
}
if (activate) {
paletteManager.activate(layer);
}
}
void unregisterCustomLayer(final CustomLayer layer) {
// Remove from palette
Palette palette = paletteManager.unregister(layer);
// Remove tracked GUI components
app.layerSoloCheckBoxes.remove(layer);
// If the palette is now empty, remove it too
if (palette.isEmpty()) {
palette.removePropertyChangeListener(this);
paletteManager.delete(palette);
app.dockingManager.removeFrame(palette.getDockableFrame().getKey());
}
}
JPopupMenu createCustomLayerMenu(final String paletteName) {
final World2 world = app.getWorld();
JPopupMenu customLayerMenu = new BetterJPopupMenu();
JMenuItem menuItem = new JMenuItem(strings.getString("add.a.custom.object.layer") + "...");
menuItem.addActionListener(e -> {
EditLayerDialog dialog = new EditLayerDialog<>(app, world.getPlatform(), Bo2Layer.class);
dialog.setVisible(() -> {
Bo2Layer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
menuItem = new JMenuItem(strings.getString("add.a.custom.ground.cover.layer") + "...");
menuItem.addActionListener(e -> {
EditLayerDialog dialog = new EditLayerDialog<>(app, world.getPlatform(), GroundCoverLayer.class);
dialog.setVisible(() -> {
GroundCoverLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
final Dimension dimension = app.getDimension();
final Dimension.Anchor anchor = dimension.getAnchor();
menuItem = new JMenuItem(strings.getString("add.a.custom.underground.pockets.layer") + "...");
menuItem.addActionListener(e -> {
UndergroundPocketsDialog dialog = new UndergroundPocketsDialog(app, world.getPlatform(), MixedMaterial.create(world.getPlatform(), Material.IRON_BLOCK), app.getColourScheme(), dimension.getMinHeight(), dimension.getMaxHeight(), world.isExtendedBlockIds());
dialog.setVisible(() -> {
UndergroundPocketsLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
if (anchor.role == CAVE_FLOOR) {
menuItem.setEnabled(false);
}
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Add a custom cave/tunnel layer...");
menuItem.addActionListener(e -> {
final TunnelLayer layer = new TunnelLayer("Tunnels", BLACK);
final int baseHeight, waterLevel;
final TileFactory tileFactory = dimension.getTileFactory();
if (tileFactory instanceof HeightMapTileFactory) {
baseHeight = (int) ((HeightMapTileFactory) tileFactory).getBaseHeight();
waterLevel = ((HeightMapTileFactory) tileFactory).getWaterHeight();
layer.setFloodWithLava(((HeightMapTileFactory) tileFactory).isFloodWithLava());
} else {
baseHeight = 58;
waterLevel = DEFAULT_WATER_LEVEL;
}
// TODO passing in dimension here is a crude mechanism. It is supposed to be the dimension on which this
// layer will be used, but that is impossible to enforce. In practice this will usually be right though
final TunnelLayerDialog dialog = new TunnelLayerDialog(app, world.getPlatform(), layer, dimension, world.isExtendedBlockIds(), app.getColourScheme(), app.getCustomBiomeManager(), dimension.getMinHeight(), dimension.getMaxHeight(), baseHeight, waterLevel);
dialog.setVisible(() -> {
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
if (anchor.role == CAVE_FLOOR) {
menuItem.setEnabled(false);
}
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Add a custom plants layer...");
menuItem.addActionListener(e -> {
final EditLayerDialog dialog = new EditLayerDialog<>(app, world.getPlatform(), PlantLayer.class);
dialog.setVisible(() -> {
final PlantLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Add a combined layer...");
menuItem.addActionListener(e -> {
final EditLayerDialog dialog = new EditLayerDialog<>(app, world.getPlatform(), CombinedLayer.class);
dialog.setVisible(() -> {
// TODO: get saved layer
final CombinedLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Add a custom annotations layer...");
menuItem.addActionListener(e -> {
final CustomAnnotationLayerDialog dialog = new CustomAnnotationLayerDialog(app, new CustomAnnotationLayer("My Custom Annotation", "A custom annotations layer", YELLOW));
dialog.setVisible(() -> {
final CustomAnnotationLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
List> allPluginLayers = new ArrayList<>();
for (CustomLayerProvider layerProvider: WPPluginManager.getInstance().getPlugins(CustomLayerProvider.class)) {
allPluginLayers.addAll(layerProvider.getCustomLayers());
}
if (! allPluginLayers.isEmpty()) {
customLayerMenu.addSeparator();
for (Class extends CustomLayer> customLayerClass: allPluginLayers) {
menuItem = new JMenuItem("Add a " + customLayerClass.getSimpleName() + " layer..."); // TODO: introduce a proper display name for custom layers
menuItem.addActionListener(e -> {
final EditLayerDialog dialog = new EditLayerDialog<>(app, world.getPlatform(), (Class) customLayerClass);
dialog.setVisible(() -> {
// TODO: get saved layer
CustomLayer layer = dialog.getLayer();
if (paletteName != null) {
layer.setPalette(paletteName);
}
registerCustomLayer(layer, true);
});
});
customLayerMenu.add(menuItem);
}
customLayerMenu.addSeparator();
}
menuItem = new JMenu("Copy layer from another dimension");
menuItem.setToolTipText("This will make a duplicate of the layer, with its own identity and separate settings");
final Function filter = app.getLayerFilterForCurrentDimension();
List copyMenuItems = getCopyLayerMenuItems((paletteName != null) ? paletteName : "Custom Layers", filter);
if (! copyMenuItems.isEmpty()) {
for (JMenuItem copyMenuItem: copyMenuItems) {
((JMenu) menuItem).add(copyMenuItem);
}
} else {
menuItem.setEnabled(false);
}
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Import custom layer(s) from file...");
menuItem.addActionListener(e -> app.importLayers(paletteName, filter));
customLayerMenu.add(menuItem);
menuItem = new JMenuItem("Import custom layer(s) from another world...");
menuItem.addActionListener(e -> app.importCustomItemsFromWorld(CustomItemsTreeModel.ItemType.LAYER, filter));
customLayerMenu.add(menuItem);
return customLayerMenu;
}
void editCustomLayer(CustomLayer layer, Runnable callback) {
final Object previousPaint = layer.getPaint();
final float previousOpacity = layer.getOpacity();
final BufferedImage previousIcon = layer.getIcon();
final AbstractEditLayerDialog dialog = createEditLayerDialog(layer);
dialog.setVisible(() -> {
final App.LayerControls layerControls = app.layerControls.get(layer);
final JComponent control = (layerControls != null) ? layerControls.control : null;
if (control != null) {
if (control instanceof AbstractButton) {
((AbstractButton) control).setText(layer.getName());
}
control.setToolTipText(layer.getName() + ": " + layer.getDescription() + "; right-click for options");
}
final Object newPaint = layer.getPaint();
final float newOpacity = layer.getOpacity();
final BufferedImage newIcon = layer.getIcon();
boolean viewRefreshed = false;
if ((! Objects.equals(newIcon, previousIcon)) && (control instanceof AbstractButton)) {
((AbstractButton) control).setIcon(new ImageIcon(layer.getIcon()));
}
if ((! Objects.equals(newPaint, previousPaint)) || (newOpacity != previousOpacity)) {
app.view.refreshTilesForLayer(layer, false);
viewRefreshed = true;
}
app.getDimension().changed();
if (layer instanceof CombinedLayer) {
updateHiddenLayers();
}
if ((layer instanceof TunnelLayer) && (! viewRefreshed)) {
app.view.refreshTilesForLayer(layer, false);
}
if (callback != null) {
callback.run();
}
});
}
void deleteCustomLayer(CustomLayer layer) {
final Dimension dimension = app.getDimension();
if ((app.getActiveOperation() instanceof PaintOperation) && (app.paint instanceof LayerPaint) && (((LayerPaint) app.paint).getLayer() == layer)) {
app.deselectPaint();
}
dimension.setEventsInhibited(true);
try {
dimension.clearLayerData(layer);
if ((layer instanceof TunnelLayer) && (((TunnelLayer) layer).getFloorDimensionId() != null)) {
final Dimension.Anchor anchor = dimension.getAnchor();
app.getWorld().removeDimension(new Dimension.Anchor(anchor.dim, CAVE_FLOOR, anchor.invert, ((TunnelLayer) layer).getFloorDimensionId()));
}
dimension.clearUndo();
} finally {
dimension.setEventsInhibited(false);
}
unregisterCustomLayer(layer);
boolean visibleLayersChanged = false;
if (app.getHiddenLayers().contains(layer)) {
app.hiddenLayers.remove(layer);
visibleLayersChanged = true;
}
if (layer.equals(app.soloLayer)) {
app.soloLayer = null;
visibleLayersChanged = true;
}
if (layer instanceof LayerContainer) {
boolean layersUnhidden = false;
for (Layer subLayer : ((LayerContainer) layer).getLayers()) {
if ((subLayer instanceof CustomLayer) && ((CustomLayer) subLayer).isHide()) {
((CustomLayer) subLayer).setHide(false);
layersUnhidden = true;
}
}
if (layersUnhidden) {
updateHiddenLayers();
visibleLayersChanged = false;
}
}
if (visibleLayersChanged) {
app.updateLayerVisibility();
}
}
void deleteUnusedLayers() {
final Dimension dimension = app.getDimension();
if (dimension == null) {
DesktopUtils.beep();
return;
}
final List unusedLayers = getCustomLayers();
final Set layersInUse = dimension.getAllLayers(true);
unusedLayers.removeAll(layersInUse);
if (unusedLayers.isEmpty()) {
showInfo(app, "There are no unused layers in this dimension.", "No Unused Layers");
} else {
final DeleteLayersDialog dialog = new DeleteLayersDialog(app, unusedLayers);
dialog.setVisible(true);
if (! dialog.isCancelled()) {
showInfo(app, "The selected layers have been deleted.", "Layers Deleted");
}
}
}
/**
* Gets all currently loaded custom layers, including hidden ones (from the
* panel or the view), regardless of whether they are used on the map.
*/
List getCustomLayers() {
final List customLayers = new ArrayList<>(256);
customLayers.addAll(paletteManager.getLayers());
customLayers.addAll(app.layersWithNoButton);
customLayers.sort(comparing(CustomLayer::getName));
return customLayers;
}
/**
* Gets all currently loaded custom layers, including hidden ones (from the
* panel or the view), regardless of whether they are used on the map, by
* palette (which will be {@code null} for hidden layers). For the
* visible layers the collections will be in the order they are displayed on
* the palette.
*/
Map> getCustomLayersByPalette() {
Map> customLayers = paletteManager.getLayersByPalette();
if (! app.layersWithNoButton.isEmpty()) {
customLayers.put(null, app.layersWithNoButton);
}
return customLayers;
}
/**
* Import the custom layer, and if it is a combined layer, also the contained layers, terrain, etc.
*
* @param layer The layer to import.
* @return {@code true} if new custom terrains were imported.
*/
boolean importCustomLayer(CustomLayer layer) {
boolean customTerrainButtonsAdded = false;
layer.setExportIndex(null);
registerCustomLayer(layer, true);
if (layer instanceof CombinedLayer) {
final CombinedLayer combinedLayer = (CombinedLayer) layer;
importLayersFromCombinedLayer(combinedLayer);
if (! combinedLayer.restoreCustomTerrain()) {
showWarning(app, "The layer contained a Custom Terrain which could not be restored. The terrain has been reset.", "Custom Terrain Not Restored");
} else {
// Check for a custom terrain type and if necessary make sure it has a button
final Terrain terrain = combinedLayer.getTerrain();
if ((terrain != null) && terrain.isCustom()) {
if (app.customMaterialButtons[terrain.getCustomTerrainIndex()] == null) {
customTerrainButtonsAdded = true;
app.addButtonForNewCustomTerrain(terrain.getCustomTerrainIndex(), Terrain.getCustomMaterial(terrain.getCustomTerrainIndex()), false);
}
}
}
}
return customTerrainButtonsAdded;
}
private void updateHiddenLayers() {
// Hide newly hidden layers
paletteManager.getLayers().stream().filter(CustomLayer::isHide).forEach(layer -> {
if ((app.getActiveOperation() instanceof PaintOperation) && (app.paint instanceof LayerPaint) && (((LayerPaint) app.paint).getLayer().equals(layer))) {
app.deselectPaint();
}
unregisterCustomLayer(layer);
app.hiddenLayers.remove(layer);
if (layer.equals(app.soloLayer)) {
app.soloLayer = null;
}
app.layersWithNoButton.add(layer);
});
// Show newly unhidden layers
for (Iterator i = app.layersWithNoButton.iterator(); i.hasNext(); ) {
CustomLayer layer = i.next();
if (! layer.isHide()) {
i.remove();
registerCustomLayer(layer, false);
}
}
app.updateLayerVisibility();
}
private void importLayersFromCombinedLayer(CombinedLayer combinedLayer) {
combinedLayer.getLayers().stream().filter(layer -> (layer instanceof CustomLayer) && (! paletteManager.contains(layer)) && (! app.layersWithNoButton.contains(layer))).forEach(layer -> {
final CustomLayer customLayer = (CustomLayer) layer;
customLayer.setExportIndex(null);
if (customLayer.isHide()) {
app.layersWithNoButton.add(customLayer);
} else {
registerCustomLayer(customLayer, false);
}
if (layer instanceof CombinedLayer) {
importLayersFromCombinedLayer((CombinedLayer) customLayer);
}
});
}
private void editCustomLayer(CustomLayer layer) {
editCustomLayer(layer, null);
}
private void exportLayer(CustomLayer layer) {
Configuration config = Configuration.getInstance();
File layerDirectory = config.getLayerDirectory();
if ((layerDirectory == null) || (! layerDirectory.isDirectory())) {
layerDirectory = DesktopUtils.getDocumentsFolder();
}
File selectedFile = FileUtils.selectFileForSave(app, "Export WorldPainter layer file", new File(layerDirectory, org.pepsoft.util.FileUtils.sanitiseName(layer.getName()) + ".layer"), new FileFilter() {
@Override
public boolean accept(File f) {
return f.isDirectory() || f.getName().toLowerCase().endsWith(".layer");
}
@Override
public String getDescription() {
return "WorldPainter Custom Layers (*.layer)";
}
@Override
public String getExtensions() {
return "*.layer";
}
});
if (selectedFile != null) {
if (!selectedFile.getName().toLowerCase().endsWith(".layer")) {
selectedFile = new File(selectedFile.getPath() + ".layer");
}
if (selectedFile.isFile() && (showConfirmDialog(app, "The file " + selectedFile.getName() + " already exists.\nDo you want to overwrite it?", "Overwrite File", YES_NO_OPTION) == NO_OPTION)) {
return;
}
try {
try (ObjectOutputStream out = new ObjectOutputStream(new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(selectedFile))))) {
out.writeObject(layer);
}
} catch (IOException e) {
throw new RuntimeException("I/O error while trying to write " + selectedFile, e);
}
config.setLayerDirectory(selectedFile.getParentFile());
showInfo(app, "Layer " + layer.getName() + " exported successfully", "Success");
}
}
private void moveLayerToPalette(CustomLayer layer, Palette destPalette) {
Palette srcPalette = paletteManager.move(layer, destPalette);
if (srcPalette.isEmpty()) {
app.dockingManager.removeFrame(srcPalette.getDockableFrame().getKey());
srcPalette.removePropertyChangeListener(this);
paletteManager.delete(srcPalette);
}
app.validate();
}
private void createNewLayerPalette(CustomLayer layer) {
String name;
if ((name = showInputDialog(app, "Enter a unique name for the new palette:", "New Palette", QUESTION_MESSAGE)) != null) {
name = name.trim();
if (name.isEmpty()) {
beepAndShowError(app, "Palette name cannot be empty", "Invalid Name");
return;
}
if (paletteManager.getPalette(name) != null) {
showMessageDialog(app, "There is already a palette with that name!", "Duplicate Name", ERROR_MESSAGE);
return;
}
Palette destPalette = paletteManager.create(name);
app.dockingManager.addFrame(destPalette.getDockableFrame());
app.dockingManager.dockFrame(destPalette.getDockableFrame().getKey(), DockContext.DOCK_SIDE_WEST, 3);
moveLayerToPalette(layer, destPalette);
app.dockingManager.activateFrame(destPalette.getDockableFrame().getKey());
destPalette.addPropertyChangeListener(this);
}
}
@SuppressWarnings("unchecked") // Guaranteed by code
@NotNull
private AbstractEditLayerDialog createEditLayerDialog(L layer) {
final World2 world = app.getWorld();
final Dimension dimension = app.getDimension();
final AbstractEditLayerDialog dialog;
if ((layer instanceof Bo2Layer) || (layer instanceof GroundCoverLayer) || (layer instanceof CombinedLayer) || (layer instanceof PlantLayer)) {
dialog = new EditLayerDialog<>(app, world.getPlatform(), layer);
} else if (layer instanceof UndergroundPocketsLayer) {
dialog = (AbstractEditLayerDialog) new UndergroundPocketsDialog(app, world.getPlatform(), (UndergroundPocketsLayer) layer, app.getColourScheme(), dimension.getMinHeight(), dimension.getMaxHeight(), world.isExtendedBlockIds());
} else if (layer instanceof TunnelLayer) {
final int baseHeight, waterLevel;
final TileFactory tileFactory = dimension.getTileFactory();
if (tileFactory instanceof HeightMapTileFactory) {
baseHeight = (int) ((HeightMapTileFactory) tileFactory).getBaseHeight();
waterLevel = ((HeightMapTileFactory) tileFactory).getWaterHeight();
} else {
baseHeight = 58;
waterLevel = DEFAULT_WATER_LEVEL;
}
// TODO passing in dimension here is a crude mechanism. It is supposed to be the dimension on which this
// layer is being used, but that is a lot of work to determine. In practice this will usually be right
// though
dialog = (AbstractEditLayerDialog) new TunnelLayerDialog(app, world.getPlatform(), (TunnelLayer) layer, dimension, world.isExtendedBlockIds(), app.getColourScheme(), app.getCustomBiomeManager(), dimension.getMinHeight(), dimension.getMaxHeight(), baseHeight, waterLevel);
} else if (layer instanceof CustomAnnotationLayer) {
dialog = (AbstractEditLayerDialog) new CustomAnnotationLayerDialog(app, (CustomAnnotationLayer) layer);
} else {
throw new IllegalArgumentException("Don't know how to create dialog for layer " + layer.getName());
}
return dialog;
}
private List getCopyLayerMenuItems(String targetPaletteName, Function filter) {
if (targetPaletteName == null) {
throw new NullPointerException("targetPaletteName");
}
final List menuItems = new ArrayList<>();
final Dimension currentDimension = app.getDimension();
for (Dimension dimension: app.getWorld().getDimensions()) {
if (dimension == currentDimension) {
continue;
}
final Map menusForDimension = new HashMap<>();
for (CustomLayer layer: dimension.getCustomLayers()) {
if ((! layer.isExportableToFile()) || ((filter != null) && (! filter.apply(layer)))) {
continue;
}
final String palette = layer.getPalette();
final JMenuItem menuForPalette = menusForDimension.computeIfAbsent(palette, k -> new JMenu(palette));
final JMenuItem menuItem = new JMenuItem(layer.getName(), new ImageIcon(layer.getIcon()));
menuItem.addActionListener(event -> copyLayerToPalette(layer, targetPaletteName));
menuForPalette.add(menuItem);
}
final JMenu menuForDimension;
if (menusForDimension.size() == 1) {
menuForDimension = menusForDimension.values().iterator().next();
menuForDimension.setText(dimension.getName());
} else {
menuForDimension = new JMenu(dimension.getName());
for (JMenu menu: menusForDimension.values()) {
menuForDimension.add(menu);
}
}
if (menuForDimension.getItemCount() > 0) {
menuItems.add(menuForDimension);
}
}
if (menuItems.size() == 1) {
return Lists.transform(asList(((JMenu) menuItems.get(0)).getMenuComponents()), e -> (JMenuItem) e);
} else {
return menuItems;
}
}
private void copyLayerToPalette(CustomLayer layer, String paletteName) {
final CustomLayer copy = layer.clone();
copy.setPalette(paletteName);
registerCustomLayer(copy, true);
}
private final App app;
final PaletteManager paletteManager = new PaletteManager(this);
private static final ResourceBundle strings = ResourceBundle.getBundle("org.pepsoft.worldpainter.resources.strings"); // NOI18N
private static final String EDITING_FLOOR_DIMENSION_KEY = "org.pepsoft.worldpainter.TunnelLayer.editingFloorDimension";
private static final Icon ADD_CUSTOM_LAYER_BUTTON_ICON = IconUtils.loadScaledIcon("org/pepsoft/worldpainter/icons/plus.png");
private static final Logger logger = LoggerFactory.getLogger(CustomLayerController.class);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy