From 82920a9c37b36cc2f7938520f3f50f03811c9d4c Mon Sep 17 00:00:00 2001 From: Thijs Wiefferink Date: Sat, 12 Jun 2021 21:50:46 +0200 Subject: [PATCH] adds customizable tagging: none/preset/search/address - adds radio selection between a couple different tagging styles: - none: no tagging - preset search dialog: open the F3 preset search - specific preset: open the dialog of the preset that is configured (or directly apply it's tags) - address: the existing address dialog - revamp settings menu: - group related settings together in a block with label - put detailed instructions into tooltips (cleaner UI) - tagging preset select dialog - use the official plugin directory instead of the working directory for saving debug files --- .gitignore | 4 + .../areaselector/AreaSelectorAction.java | 250 +++++++++--- .../plugins/areaselector/ImageAnalyzer.java | 26 +- .../preferences/PreferencesPanel.java | 361 +++++++++++++----- .../TaggingPresetSelectionSearch.java | 61 +++ 5 files changed, 555 insertions(+), 147 deletions(-) create mode 100644 src/org/openstreetmap/josm/plugins/areaselector/preferences/TaggingPresetSelectionSearch.java diff --git a/.gitignore b/.gitignore index 3a2ab8c..db6bc47 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ lib/*-source.jar lib/*-jar.jar lib/*-maven-plugin.jar lib/*-bundle.jar +javadoc +# Intellij project files +*.iml + diff --git a/src/org/openstreetmap/josm/plugins/areaselector/AreaSelectorAction.java b/src/org/openstreetmap/josm/plugins/areaselector/AreaSelectorAction.java index 701de85..7d9e0f8 100644 --- a/src/org/openstreetmap/josm/plugins/areaselector/AreaSelectorAction.java +++ b/src/org/openstreetmap/josm/plugins/areaselector/AreaSelectorAction.java @@ -32,6 +32,7 @@ import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.command.AddCommand; import org.openstreetmap.josm.command.ChangeCommand; +import org.openstreetmap.josm.command.ChangePropertyCommand; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.DeleteCommand; import org.openstreetmap.josm.command.PseudoCommand; @@ -43,6 +44,7 @@ import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; +import org.openstreetmap.josm.data.osm.Tag; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.DoubleProperty; @@ -52,6 +54,17 @@ import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSearchDialog; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; +import org.openstreetmap.josm.gui.tagging.presets.items.Check; +import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup; +import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect; +import org.openstreetmap.josm.gui.tagging.presets.items.Key; +import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; +import org.openstreetmap.josm.gui.tagging.presets.items.Text; import org.openstreetmap.josm.plugins.austriaaddresshelper.AustriaAddressHelperAction; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; @@ -68,14 +81,19 @@ public class AreaSelectorAction extends MapMode implements MouseListener { toleranceAngle = ImageAnalyzer.DEFAULT_TOLERANCEANGLE; protected boolean showAddressDialog = true, mergeNodes = true, useAustriaAdressHelper = false, - replaceBuildings = true, addSourceTag = false; + replaceBuildings = true, addSourceTag = false, applyPresetDirectly = false; + protected String taggingStyle = "none"; + protected String taggingPresetName = ""; public static final String PLUGIN_NAME = "areaselector"; public static final String KEY_SHOWADDRESSDIALOG = PLUGIN_NAME + ".showaddressdialog", + KEY_TAGGINGSTYLE = PLUGIN_NAME + ".taggingstyle", + KEY_TAGGINGPRESETNAME = PLUGIN_NAME + ".taggingpresetname", KEY_MERGENODES = PLUGIN_NAME + ".mergenodes", KEY_AAH = PLUGIN_NAME + ".austriaadresshelper", KEY_REPLACEBUILDINGS = PLUGIN_NAME + ".replacebuildings", - KEY_ADDSOURCETAG = PLUGIN_NAME + ".addsourcetag"; + KEY_ADDSOURCETAG = PLUGIN_NAME + ".addsourcetag", + KEY_APPLYPRESETDIRECTLY = PLUGIN_NAME + ".applypresetdirectly"; protected Logger log = LogManager.getLogger(AreaSelectorAction.class.getCanonicalName()); @@ -94,9 +112,12 @@ public AreaSelectorAction(MapFrame mapFrame) { protected void readPrefs() { this.mergeNodes = new BooleanProperty(KEY_MERGENODES, true).get(); this.showAddressDialog = new BooleanProperty(KEY_SHOWADDRESSDIALOG, true).get(); + this.taggingStyle = new StringProperty(KEY_TAGGINGSTYLE, "none").get(); + this.taggingPresetName = new StringProperty(KEY_TAGGINGPRESETNAME, "").get(); useAustriaAdressHelper = new BooleanProperty(KEY_AAH, false).get(); replaceBuildings = new BooleanProperty(KEY_REPLACEBUILDINGS, true).get(); addSourceTag = new BooleanProperty(KEY_ADDSOURCETAG, false).get(); + applyPresetDirectly = new BooleanProperty(KEY_APPLYPRESETDIRECTLY, false).get(); } private static Cursor getCursor() { @@ -179,7 +200,7 @@ public BufferedImage getLayeredImage() { } public void createArea() { - + ////////// Polygon generation MapView mapView = MainApplication.getMap().mapView; BufferedImage bufImage = getLayeredImage(); @@ -196,66 +217,149 @@ public void createArea() { imgAnalyzer.setToleranceDist(toleranceInPixel); Polygon polygon = imgAnalyzer.getArea(); + if (polygon == null) { + JOptionPane.showMessageDialog(MainApplication.getMap(), tr("Unable to detect a polygon where you clicked."), + tr("Area Selector"), JOptionPane.WARNING_MESSAGE); + return; + } + + Way way = createWayFromPolygon(mapView, polygon); + Way newWay = null; + + DataSet ds = MainApplication.getLayerManager().getEditDataSet(); + Collection cmds = new LinkedList<>(); + List nodes = way.getNodes(); + for (int i = 0; i < nodes.size() - 1; i++) { + cmds.add(new AddCommand(ds, nodes.get(i))); + } + cmds.add(new AddCommand(ds, way)); + UndoRedoHandler.getInstance().add(new SequenceCommand(/* I18n: Name of the command that adds the generated way */ tr("create closed way"), cmds)); - if (polygon != null) { + if (replaceBuildings) { Way existingWay = MainApplication.getMap().mapView.getNearestWay(clickPoint, OsmPrimitive::isUsable); + if (existingWay != null && way.getBBox().bounds(existingWay.getBBox().getCenter())) { + log.info("existing way is inside of new building: " + existingWay.toString() + " is in " + way.toString()); + UndoRedoHandler.getInstance().add(new SequenceCommand(tr("replace building"), replaceWay(existingWay, way))); + way = existingWay; + } + } - Way way = createWayFromPolygon(mapView, polygon), newWay = null; + ds.setSelected(way); - way.put(AddressDialog.TAG_BUILDING, new StringProperty(AddressDialog.PREF_BUILDING, "yes").get()); + if (mergeNodes) { + mergeNodes(way); + } - if (!showAddressDialog && addSourceTag) { - ArrayList sources = new ArrayList<>(); - for (Layer layer : mapView.getLayerManager().getVisibleLayersInZOrder()) { - if (layer.isVisible() && layer.isBackgroundLayer()) { - sources.add(layer.getName()); - } + if (useAustriaAdressHelper) { + Map newAddress = fetchAddress(way); + if (newAddress != null) { + newWay = new Way(way); + newWay.setKeys(newAddress); + log.info("Found attributes: {}", newWay.getKeys()); + if (!showAddressDialog) { + final List commands = new ArrayList<>(); + commands.add(new ChangeCommand(way, newWay)); + UndoRedoHandler.getInstance().add( + new SequenceCommand(trn("Add address", "Add addresses", commands.size()), commands)); } - Collections.reverse(sources); - String source = sources.stream().map(Object::toString).collect(Collectors.joining("; ")).toString(); - if (!source.isEmpty()) { - way.put(AddressDialog.TAG_SOURCE, source); + } + } + + if (!showAddressDialog && addSourceTag) { + ArrayList sources = new ArrayList<>(); + for (Layer layer : mapView.getLayerManager().getVisibleLayersInZOrder()) { + if (layer.isVisible() && layer.isBackgroundLayer()) { + sources.add(layer.getName()); } } + Collections.reverse(sources); + String source = sources.stream().map(Object::toString).collect(Collectors.joining("; ")); + if (!source.isEmpty()) { + way.put(AddressDialog.TAG_SOURCE, source); + } + } - DataSet ds = MainApplication.getLayerManager().getEditDataSet(); - Collection cmds = new LinkedList<>(); - List nodes = way.getNodes(); - for (int i = 0; i < nodes.size() - 1; i++) { - cmds.add(new AddCommand(ds, nodes.get(i))); + ////////// Tagging + if (taggingStyle.equals("none")) { + // Do nothing + } else if (taggingStyle.equals("presetSearchDialog")) { + if (getLayerManager().getActiveData() == null) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("There is no active data layer, cannot show preset search dialog."), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; } - cmds.add(new AddCommand(ds, way)); - UndoRedoHandler.getInstance().add(new SequenceCommand(/* I18n: Name of command */ tr("create building"), cmds)); - - if (replaceBuildings && existingWay != null) { - if (way.getBBox().bounds(existingWay.getBBox().getCenter())) { - log.info("existing way is inside of new building: "+existingWay.toString() + " is in " + way.toString()); - UndoRedoHandler.getInstance().add(new SequenceCommand(tr("replace building"), replaceWay(existingWay, way))); - way = existingWay; - } + TaggingPresetSearchDialog.getInstance().showDialog(); + } else if (taggingStyle.equals("specificPreset")) { + if (taggingPresetName.isEmpty()) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("No preset name configured in the settings, enter one."), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; } - ds.setSelected(way); + // Find the configured preset + TaggingPreset taggingPresetToApply = null; + for (TaggingPreset taggingPreset : TaggingPresets.getTaggingPresets()) { + if (taggingPreset.getRawName().equals(taggingPresetName)) { + taggingPresetToApply = taggingPreset; + break; + } + } + if (taggingPresetToApply == null) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("Could not find configured tagging preset: '{0}'.", taggingPresetName), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; + } - if (mergeNodes) { - mergeNodes(way); + // Check that tagging preset can be applied to closed ways + if (!taggingPresetToApply.types.contains(TaggingPresetType.CLOSEDWAY)) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("Selected preset is not suitable for a closed way, select another one: '{0}'.", taggingPresetName), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; } - if (useAustriaAdressHelper) { - Map newAddress = fetchAddress(way); - if (newAddress != null) { - newWay = new Way(way); - newWay.setKeys(newAddress); - log.info("Found attributes: {}", newWay.getKeys()); - if (!showAddressDialog) { - final List commands = new ArrayList<>(); - commands.add(new ChangeCommand(way, newWay)); - UndoRedoHandler.getInstance().add( - new SequenceCommand(trn("Add address", "Add addresses", commands.size()), commands)); - } + if (applyPresetDirectly) { + // Directly apply the tags of the configured preset + Collection tags = getTagsForPreset(taggingPresetToApply); + Collection commands = new ArrayList<>(); + for (Tag tag : tags) { + commands.add(new ChangePropertyCommand(way, tag.getKey(), tag.getValue())); } - } + if (commands.isEmpty()) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("Tagging preset '{0}' does not have any tags to apply.", taggingPresetToApply.getRawName()), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; + } + + // Apply the command + Command cmd = SequenceCommand.wrapIfNeeded(tr("Apply tagging preset default values: {0}", taggingPresetToApply.getRawName()), commands); + UndoRedoHandler.getInstance().add(cmd); + } else { + // Show the dialog of the configured taggin preset + taggingPresetToApply.showAndApply(Collections.singleton(way)); + } + } else if (taggingStyle.equals("address")) { + way.put(AddressDialog.TAG_BUILDING, new StringProperty(AddressDialog.PREF_BUILDING, "yes").get()); if (showAddressDialog) { if (newWay == null) { newWay = way; @@ -263,11 +367,57 @@ public void createArea() { new AddressDialog(newWay, way).showAndSave(); } } else { - JOptionPane.showMessageDialog(MainApplication.getMap(), tr("Unable to detect a polygon where you clicked."), - tr("Area Selector"), JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("Unknown tagging style setting: '{0}'.", taggingStyle), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); } } + /** + * Get all tags that should be applied directly for a given TaggingPreset + */ + public Collection getTagsForPreset(TaggingPreset taggingPreset) { + Collection tags = new ArrayList<>(); + for (TaggingPresetItem item : taggingPreset.data) { + // Determine key (used for most single-tag TaggingPresetItem's) + String key = null; + if (item instanceof KeyedItem) { + key = ((KeyedItem) item).key; + } + + // Detect item type + if (item instanceof Text) { + addTag(tags, key, ((Text) item).default_); + } else if (item instanceof Key) { + addTag(tags, key, ((Key) item).value); + } else if (item instanceof Check) { + addTag(tags, key, ((Check) item).default_); + } else if (item instanceof CheckGroup) { + for (Check check : ((CheckGroup) item).checks) { + addTag(tags, check.key, check.default_); + } + } else if (item instanceof ComboMultiSelect) { + addTag(tags, key, ((ComboMultiSelect) item).default_); + } + // Ignore other TaggingPresetItem's: like spacing/links/etc. + } + + return tags; + } + + /** + * Add a Tag to a Collection only when the key/value have proper values + */ + private void addTag(Collection tags, String key, String value) { + if (key == null || key.equals("") || value == null || value.equals("")) { + return; + } + tags.add(new Tag(key, value)); + } + /** * fetch Address using Austria Address Helper */ @@ -399,10 +549,6 @@ public Command mergeNode(Node node) { return MergeNodesAction.mergeNodes(selectedNodes, targetNode, targetLocationNode); } - /** - * @param prefs - * the prefs to set - */ public void setPrefs() { this.readPrefs(); } diff --git a/src/org/openstreetmap/josm/plugins/areaselector/ImageAnalyzer.java b/src/org/openstreetmap/josm/plugins/areaselector/ImageAnalyzer.java index 2c42a4e..3ac4454 100644 --- a/src/org/openstreetmap/josm/plugins/areaselector/ImageAnalyzer.java +++ b/src/org/openstreetmap/josm/plugins/areaselector/ImageAnalyzer.java @@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.openstreetmap.josm.data.Preferences; import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.DoubleProperty; import org.openstreetmap.josm.data.preferences.IntegerProperty; @@ -906,18 +907,28 @@ public boolean saveImgToFile(boolean[][]image, String filename) { return saveImgToFile(convertBinaryMatrixToImage(image), filename); } + private File getPluginFolder() { + // Our own folder in the the plugins directory + return new File(Preferences.main().getPluginsDirectory(), "areaselector"); + } + + private File getDebugImageFolder() { + return new File(getPluginFolder(), "debug"); + } + public boolean saveImgToFile(BufferedImage buf, String filename) { try { - File folder = new File("test"); + // Put the image into the plugins directory, in our own folder + File folder = getDebugImageFolder(); if (!folder.exists()) { folder.mkdirs(); } if (!folder.isDirectory()) { - log.warn("test is not a folder, but a file"); + log.warn("debug folder target is a file instead of a folder: "+folder.getAbsolutePath()); return false; } else { ImageIO.write(buf, IMG_TYPE, - new File("test/"+(fileCount < 10 ? "0" : "")+(fileCount++)+"_"+filename+"."+IMG_TYPE.toLowerCase())); + new File(folder, (fileCount < 10 ? "0" : "")+(fileCount++)+"_"+filename+"."+IMG_TYPE.toLowerCase())); return true; } } catch (Exception e) { @@ -927,10 +938,13 @@ public boolean saveImgToFile(BufferedImage buf, String filename) { } public static BufferedImage getImgFromFile(String filename) { + return getImgFromFile(new File(filename+"."+IMG_TYPE.toLowerCase())); + } + public static BufferedImage getImgFromFile(File file) { try { - return ImageIO.read(new File(filename+"."+IMG_TYPE.toLowerCase())); + return ImageIO.read(file); } catch (IOException e) { - log.warn("unable to read file "+filename, e); + log.warn("unable to read file "+file.getAbsolutePath(), e); } return null; } @@ -939,7 +953,7 @@ public static BufferedImage getImgFromFile(String filename) { * find out if marvin fits our needs. */ public void testMarvin() { - MarvinImage mImg = new MarvinImage(getImgFromFile("test/boundary_in")); + MarvinImage mImg = new MarvinImage(getImgFromFile(new File(getPluginFolder(), "boundary_in"))); MarvinImage inverted = applyPlugin("org.marvinproject.image.color.invert", mImg); MarvinImage blackAndWhite = MarvinColorModelConverter.rgbToBinary(inverted, 127); MarvinImage boundary = applyPlugin("org.marvinproject.image.morphological.boundary", blackAndWhite); diff --git a/src/org/openstreetmap/josm/plugins/areaselector/preferences/PreferencesPanel.java b/src/org/openstreetmap/josm/plugins/areaselector/preferences/PreferencesPanel.java index ee68d38..010d27f 100644 --- a/src/org/openstreetmap/josm/plugins/areaselector/preferences/PreferencesPanel.java +++ b/src/org/openstreetmap/josm/plugins/areaselector/preferences/PreferencesPanel.java @@ -3,21 +3,37 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JTextField; -import javax.swing.SpringLayout; import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.DoubleProperty; import org.openstreetmap.josm.data.preferences.IntegerProperty; +import org.openstreetmap.josm.data.preferences.StringProperty; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; import org.openstreetmap.josm.plugins.areaselector.AreaSelectorAction; import org.openstreetmap.josm.plugins.areaselector.ImageAnalyzer; +import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.Logging; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + /** * Area Selector Preferences JPanel * @@ -33,6 +49,8 @@ public class PreferencesPanel extends JPanel { private JTextField txtThinningIterations; + private JLabel lblPresetName; + private JCheckBox ckbxShowAddressDialog; private JCheckBox ckbxMergeNodes; @@ -45,12 +63,14 @@ public class PreferencesPanel extends JPanel { private JComboBox algorithm; - protected JComponent ref; - private JCheckBox ckbxReplaceBuilding; private JCheckBox ckbxAddSourceTag; + private JCheckBox ckbxApplyPresetDirectly; + + private Map taggingStyleOptions; + /** * Constructs a new {@code PreferencesPanel}. */ @@ -63,116 +83,258 @@ public PreferencesPanel() { * Initialize the contents of the frame. */ private void initialize() { - ref = this; + // Column of components, with some spacing between rows + this.setLayout(new GridBagLayout()); - SpringLayout sl_panel = new SpringLayout(); - this.setLayout(sl_panel); + ////////// Polygon generation + JPanel polygonGenerationPanel = new JPanel(new GridLayout(0, 1, 0, 5)); String[] algorithms = {tr("Auto"), tr("Boofcv - high resolution images"), tr("Custom - low resolution images")}; algorithm = new JComboBox<>(algorithms); algorithm.setSelectedIndex(0); + this.addInput( + polygonGenerationPanel, + tr("Algorithm"), + algorithm, + tr("Choose wich algorithm should be used. \"Auto\" tries to find an area with Boofcv and uses the custom algorithm as a fallback."), + false + ); - // CHECKSTYLE.OFF: LineLength + txtThinningIterations = new JTextField(); this.addInput( - tr("Choose wich algorithm should be used. \"Auto\" tries to find an area with Boofcv and uses the custom algorithm as a fallback."), - tr("Algorithm"), algorithm); + polygonGenerationPanel, + tr("Thinning Iterations"), + txtThinningIterations, + tr("How often thinning operation should be applied (Default {0}).", ImageAnalyzer.DEFAULT_THINNING_ITERATIONS), + false + ); + + // CHECKSTYLE.OFF: LineLength txtToleranceDist = new JTextField(); this.addInput( - tr("Maximum distance in meters between a point and the line to be considered as a member of this line (Default: {0}).", - ImageAnalyzer.DEFAULT_TOLERANCEDIST), tr("Distance Tolerance"), txtToleranceDist); + polygonGenerationPanel, + tr("Distance Tolerance"), + txtToleranceDist, + tr("Maximum distance in meters between a point and the line to be considered as a member of this line (Default: {0}).", ImageAnalyzer.DEFAULT_TOLERANCEDIST), + false + ); txtToleranceAngle = new JTextField(); this.addInput( - tr("Lines with a smaller angle (degrees) than this will be combined to one line (Default {0}).", - Math.floor(Math.toDegrees(ImageAnalyzer.DEFAULT_TOLERANCEANGLE))), tr("Angle Tolerance"), txtToleranceAngle); + polygonGenerationPanel, + tr("Angle Tolerance"), + txtToleranceAngle, + tr("Lines with a smaller angle (degrees) than this will be combined to one line (Default {0}).", Math.floor(Math.toDegrees(ImageAnalyzer.DEFAULT_TOLERANCEANGLE))), + false + ); txtColorThreshold = new JTextField(); this.addInput( - tr("The color threshold defines how much a color may differ from the selected color. The red, green and blue values must be in the range of (selected - threshold) to (selected + threshold). (Default: {0}).", - ImageAnalyzer.DEFAULT_COLORTHRESHOLD), tr("Color Threshold"), txtColorThreshold); + polygonGenerationPanel, + tr("Color Threshold"), + txtColorThreshold, + tr( + "The color threshold defines how much a color may differ from the selected color. The red, green and blue values must be in the range of (selected - threshold) to (selected + threshold). (Default: {0}).", + ImageAnalyzer.DEFAULT_COLORTHRESHOLD + ), + false + ); // CHECKSTYLE.ON: LineLength - ckbxHSV = new JCheckBox("

" + tr("Use HSV based algorithm") + "

"); - this.addCheckbox(tr("Use hue and saturation instead of RGB distinction to select matching colors."), ckbxHSV); - - txtThinningIterations = new JTextField(); - this.addInput(tr("How often thinning operation should be applied (Default {0}).", ImageAnalyzer.DEFAULT_THINNING_ITERATIONS), - tr("Thinning Iterations"), txtThinningIterations); - - ckbxShowAddressDialog = new JCheckBox("

" + tr("show address dialog") + "

"); - this.addCheckbox(tr("Show Address Dialog after mapping an area"), ckbxShowAddressDialog); - - ckbxMergeNodes = new JCheckBox("

" + tr("merge nodes") + "

"); - this.addCheckbox(tr("Merge nodes with existing nodes"), ckbxMergeNodes); - - ckbxAustriaAdressHelper = new JCheckBox("

" + tr("use austria address helper") + "

"); - this.addCheckbox(tr("Automatically try to find the correct address via Austria Address Helper plugin"), ckbxAustriaAdressHelper); - - ckbxReplaceBuilding = new JCheckBox("

" + tr("Replace existing buildings") + "

"); - this.addCheckbox(tr("Replace an existing building with the new one."), ckbxReplaceBuilding); - - ckbxAddSourceTag = new JCheckBox("

" + tr("Add source tag") + "

"); - this.addCheckbox(tr("Add source tag."), ckbxAddSourceTag); - - ckbxDebug = new JCheckBox("

" + tr("Debug") + "

"); - this.addCheckbox(tr("Debugging mode will write images for each processing step."), ckbxDebug); - + ckbxHSV = new JCheckBox(tr("Use HSV based algorithm")); + this.addCheckbox( + polygonGenerationPanel, + ckbxHSV, + tr("Use hue and saturation instead of RGB distinction to select matching colors."), + false + ); + + this.addSection("Polygon generation", polygonGenerationPanel); + + ////////// Polygon processing + JPanel polygonProcessing = new JPanel(new GridLayout(0, 1, 0, 5)); + + ckbxMergeNodes = new JCheckBox(tr("merge nodes")); + this.addCheckbox( + polygonProcessing, + ckbxMergeNodes, + tr("Merge nodes with existing nodes"), + false + ); + + ckbxReplaceBuilding = new JCheckBox(tr("Replace existing buildings")); + this.addCheckbox( + polygonProcessing, + ckbxReplaceBuilding, + tr("Replace an existing building with the new one."), + false + ); + + this.addSection("Polygon processing", polygonProcessing); + + ////////// Tagging + JPanel tagging = new JPanel(new GridLayout(0, 1, 0, 5)); + ButtonGroup taggingStyle = new ButtonGroup(); + + taggingStyleOptions = new HashMap<>(); + + JRadioButton none = new JRadioButton(tr("None")); + taggingStyle.add(none); + this.addRadioButton( + tagging, + none, + tr("Don't apply any tags, only create the polygon") + ); + taggingStyleOptions.put("none", none); + + JRadioButton presetSearchDialog = new JRadioButton(tr("Preset search dialog")); + taggingStyle.add(presetSearchDialog); + this.addRadioButton( + tagging, + presetSearchDialog, + tr("Don't apply any tags, only create the polygon") + ); + taggingStyleOptions.put("presetSearchDialog", presetSearchDialog); + + JRadioButton specificPreset = new JRadioButton(tr("Specific preset")); + taggingStyle.add(specificPreset); + this.addRadioButton( + tagging, + specificPreset, + tr("Don't apply any tags, only create the polygon") + ); + taggingStyleOptions.put("specificPreset", specificPreset); + + // Preset selection controls and current selected preset + lblPresetName = new JLabel(); + lblPresetName.setBorder(BorderFactory.createEmptyBorder(0, 45, 0, 0)); + tagging.add(lblPresetName, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + JPanel row = new JPanel(new GridLayout(1, 1)); + JButton selectPresetButton = new JButton("Select preset"); + selectPresetButton.addActionListener(e -> TaggingPresetSelectionSearch.show(selectedPreset -> { + if (selectedPreset == null) { + return; + } + if (!selectedPreset.types.contains(TaggingPresetType.CLOSEDWAY)) { + JOptionPane.showMessageDialog( + MainApplication.getMap(), + tr("Selected preset is not suitable for a closed way, select another one."), + tr("Area Selector"), + JOptionPane.WARNING_MESSAGE + ); + return; + } + lblPresetName.setText(selectedPreset.getRawName()); + lblPresetName.setIcon(selectedPreset.getIcon(Action.LARGE_ICON_KEY)); + })); + selectPresetButton.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + row.add(selectPresetButton); + row.setBorder(BorderFactory.createEmptyBorder(0, 43, 0, 0)); + tagging.add(row, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + + ckbxApplyPresetDirectly = new JCheckBox(tr("Apply preset tags directly")); + this.addCheckbox( + tagging, + ckbxApplyPresetDirectly, + tr("Don't show the preset dialog, but apply all non-empty tags of the preset directly to the created area"), + true + ); + + JRadioButton address = new JRadioButton(tr("Address")); + taggingStyle.add(address); + this.addRadioButton( + tagging, + address, + tr("Don't apply any tags, only create the polygon") + ); + taggingStyleOptions.put("address", address); + + ckbxShowAddressDialog = new JCheckBox(tr("Show address dialog")); + this.addCheckbox( + tagging, + ckbxShowAddressDialog, + tr("Show Address Dialog after mapping an area"), + true + ); + + ckbxAustriaAdressHelper = new JCheckBox(tr("Use austria address helper")); + this.addCheckbox( + tagging, + ckbxAustriaAdressHelper, + tr("Automatically try to find the correct address via Austria Address Helper plugin"), + true + ); + + this.addSection("Tagging", tagging); + + ckbxAddSourceTag = new JCheckBox(tr("Add source tag")); + this.addCheckbox( + tagging, + ckbxAddSourceTag, + tr("Add source tag."), + false + ); + + ////////// Other + JPanel other = new JPanel(new GridLayout(0, 1, 0, 5)); + ckbxDebug = new JCheckBox(tr("Debug")); + this.addCheckbox( + other, + ckbxDebug, + tr("Debugging mode will write images for each processing step."), + false + ); + this.addSection("Other", other); + + + // Fill up remaining space + this.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.BOTH)); } - protected void addInput(String description, String title, JComponent input) { - SpringLayout sl_panel = (SpringLayout) this.getLayout(); - - JLabel lblTitle = new JLabel("

" + title + "

"); - this.northConstraint(lblTitle); - sl_panel.putConstraint(SpringLayout.WEST, lblTitle, 20, SpringLayout.WEST, this); - this.add(lblTitle); - - sl_panel.putConstraint(SpringLayout.NORTH, input, -6, SpringLayout.NORTH, lblTitle); - sl_panel.putConstraint(SpringLayout.EAST, input, -140, SpringLayout.EAST, this); + /** + * Add section with a label + */ + protected void addSection(String name, JPanel section) { + final JLabel lbl = new JLabel("

" + name + "

"); + lbl.setLabelFor(section); + this.add(lbl, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 30, 0, 10)); + this.add(section, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + } + /** + * Adds a a title, with an input next to it and a description below them + */ + protected void addInput(JPanel target, String title, JComponent input, String description, boolean nested) { + JLabel label = new JLabel("

" + title + "

"); + JPanel row = new JPanel(new GridLayout(1, 2)); if (input instanceof JTextField) { ((JTextField) input).setColumns(10); } - this.add(input); - - ref = lblTitle; - - this.addDescription(description); - } - - protected void addCheckbox(String description, JCheckBox checkbox) { - SpringLayout sl_panel = (SpringLayout) this.getLayout(); - - sl_panel.putConstraint(SpringLayout.WEST, checkbox, 0, SpringLayout.WEST, ref); - this.northConstraint(checkbox); - this.add(checkbox); - - ref = checkbox; - - this.addDescription(description); + row.add(label); + row.add(input); + row.setToolTipText(description); + if (nested) { + row.setBorder(BorderFactory.createEmptyBorder(0, 40, 0, 0)); + } + target.add(row, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } - protected JLabel addDescription(String description) { - - JLabel lblDescription = new JLabel("

" + description + "

"); - SpringLayout sl_panel = (SpringLayout) this.getLayout(); - sl_panel.putConstraint(SpringLayout.NORTH, lblDescription, 4, SpringLayout.SOUTH, ref); - sl_panel.putConstraint(SpringLayout.WEST, lblDescription, 20, SpringLayout.WEST, this); - sl_panel.putConstraint(SpringLayout.EAST, lblDescription, -20, SpringLayout.EAST, this); - this.add(lblDescription); - - ref = lblDescription; - return lblDescription; + protected void addCheckbox(JPanel target, JCheckBox checkbox, String description, boolean nested) { + JPanel row = new JPanel(new GridLayout(1, 1)); + row.add(checkbox); + row.setToolTipText(description); + if (nested) { + row.setBorder(BorderFactory.createEmptyBorder(0, 40, 0, 0)); + } + target.add(row, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } - protected void northConstraint(JComponent component) { - SpringLayout sl_panel = (SpringLayout) this.getLayout(); - if (ref == this) { - sl_panel.putConstraint(SpringLayout.NORTH, component, 4, SpringLayout.NORTH, ref); - } else { - sl_panel.putConstraint(SpringLayout.NORTH, component, 12, SpringLayout.SOUTH, ref); - } + protected void addRadioButton(JPanel target, JRadioButton radioButton, String description) { + JPanel row = new JPanel(new GridLayout(1, 2)); + row.add(radioButton); + row.setToolTipText(description); + target.add(row, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } /** @@ -196,17 +358,32 @@ public void savePreferences() { new BooleanProperty(AreaSelectorAction.KEY_AAH, false).put(ckbxAustriaAdressHelper.isSelected()); new BooleanProperty(AreaSelectorAction.KEY_REPLACEBUILDINGS, true).put(ckbxReplaceBuilding.isSelected()); new BooleanProperty(AreaSelectorAction.KEY_ADDSOURCETAG, false).put(ckbxAddSourceTag.isSelected()); + new BooleanProperty(AreaSelectorAction.KEY_APPLYPRESETDIRECTLY, false).put(ckbxApplyPresetDirectly.isSelected()); + + String taggingStyleSetting = "none"; + for (Map.Entry entry : taggingStyleOptions.entrySet()) { + if (entry.getValue().isSelected()) { + taggingStyleSetting = entry.getKey(); + } + } + new StringProperty(AreaSelectorAction.KEY_TAGGINGSTYLE, "none").put(taggingStyleSetting); + + new StringProperty(AreaSelectorAction.KEY_TAGGINGPRESETNAME, "").put(lblPresetName.getText()); } - /** - * @param prefs - * the prefs to set - */ public void readPreferences() { txtColorThreshold.setText(Integer.toString(new IntegerProperty(ImageAnalyzer.KEY_COLORTHRESHOLD, ImageAnalyzer.DEFAULT_COLORTHRESHOLD).get())); txtThinningIterations.setText(Integer.toString(new IntegerProperty(ImageAnalyzer.KEY_THINNING_ITERATIONS, ImageAnalyzer.DEFAULT_THINNING_ITERATIONS).get())); txtToleranceAngle.setText(Double.toString(Math.floor(Math.toDegrees(new DoubleProperty(ImageAnalyzer.KEY_TOLERANCEANGLE, ImageAnalyzer.DEFAULT_TOLERANCEANGLE).get())))); txtToleranceDist.setText(Double.toString(new DoubleProperty(ImageAnalyzer.KEY_TOLERANCEDIST, ImageAnalyzer.DEFAULT_TOLERANCEDIST).get())); + String taggingPresetName = new StringProperty(AreaSelectorAction.KEY_TAGGINGPRESETNAME, "").get(); + lblPresetName.setText(taggingPresetName); + for (TaggingPreset t : TaggingPresets.getTaggingPresets()) { + if (t.getRawName().equals(taggingPresetName)) { + lblPresetName.setIcon(t.getIcon(Action.LARGE_ICON_KEY)); + break; + } + } ckbxMergeNodes.setSelected(new BooleanProperty(AreaSelectorAction.KEY_MERGENODES, true).get()); ckbxShowAddressDialog.setSelected(new BooleanProperty(AreaSelectorAction.KEY_SHOWADDRESSDIALOG, true).get()); @@ -218,5 +395,11 @@ public void readPreferences() { ckbxDebug.setSelected(new BooleanProperty(ImageAnalyzer.KEY_DEBUG, false).get()); ckbxReplaceBuilding.setSelected(new BooleanProperty(AreaSelectorAction.KEY_REPLACEBUILDINGS, true).get()); ckbxAddSourceTag.setSelected(new BooleanProperty(AreaSelectorAction.KEY_ADDSOURCETAG, false).get()); + ckbxApplyPresetDirectly.setSelected(new BooleanProperty(AreaSelectorAction.KEY_APPLYPRESETDIRECTLY, false).get()); + + String taggingStyleSetting = new StringProperty(AreaSelectorAction.KEY_TAGGINGSTYLE, "none").get(); + for (Map.Entry entry : taggingStyleOptions.entrySet()) { + entry.getValue().setSelected(taggingStyleSetting.equals(entry.getKey())); + } } } diff --git a/src/org/openstreetmap/josm/plugins/areaselector/preferences/TaggingPresetSelectionSearch.java b/src/org/openstreetmap/josm/plugins/areaselector/preferences/TaggingPresetSelectionSearch.java new file mode 100644 index 0000000..6f30dd0 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/areaselector/preferences/TaggingPresetSelectionSearch.java @@ -0,0 +1,61 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.areaselector.preferences; + +import org.openstreetmap.josm.data.osm.event.SelectionEventManager; +import org.openstreetmap.josm.gui.ExtendedDialog; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector; + +import java.awt.event.ActionEvent; + +import static org.openstreetmap.josm.tools.I18n.tr; + +interface PresetSelectedHandler { + void presetSelected(TaggingPreset preset); +} + +/** + * The tagging presets search dialog (F3). + * @since 3388 + */ +public final class TaggingPresetSelectionSearch extends ExtendedDialog { + + private final PresetSelectedHandler presetSelectedHandler; + private final TaggingPresetSelector selector; + + /** + * Returns the unique instance of {@code TaggingPresetSearchDialog}. + * @return the unique instance of {@code TaggingPresetSearchDialog}. + */ + public static synchronized TaggingPresetSelectionSearch show(PresetSelectedHandler presetSelectedHandler) { + // Ideally there would only be presets in this dialog that can be applied to CLOSED_WAY, but that is not easy to fix + TaggingPresetSelectionSearch dialog = new TaggingPresetSelectionSearch(presetSelectedHandler); + dialog.showDialog(); + + return dialog; + } + + private TaggingPresetSelectionSearch(PresetSelectedHandler presetSelectedHandler) { + super(MainApplication.getMainFrame(), tr("Search presets"), tr("Select"), tr("Cancel")); + + this.presetSelectedHandler = presetSelectedHandler; + setButtonIcons("dialogs/search", "cancel"); + configureContextsensitiveHelp("/Action/TaggingPresetSearch", true /* show help button */); + selector = new TaggingPresetSelector(false, false); + setContent(selector, false); + SelectionEventManager.getInstance().addSelectionListener(selector); + selector.setDblClickListener(e -> buttonAction(0, null)); + + selector.init(); + } + + @Override + protected void buttonAction(int buttonIndex, ActionEvent evt) { + super.buttonAction(buttonIndex, evt); + if (buttonIndex == 0) { + TaggingPreset preset = selector.getSelectedPreset(); + presetSelectedHandler.presetSelected(preset); + } + } +}