// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.trustosm.gui.dialogs; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.geom.GeneralPath; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.osm.DataSet; 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.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.WaySegment; import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.dialogs.ToggleDialog; import org.openstreetmap.josm.gui.layer.MapViewPaintable; import org.openstreetmap.josm.plugins.trustosm.TrustOSMplugin; import org.openstreetmap.josm.plugins.trustosm.data.TrustNode; import org.openstreetmap.josm.plugins.trustosm.data.TrustOsmPrimitive; import org.openstreetmap.josm.plugins.trustosm.data.TrustSignatures; import org.openstreetmap.josm.plugins.trustosm.data.TrustWay; import org.openstreetmap.josm.plugins.trustosm.util.TrustAnalyzer; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; public class TrustDialog extends ToggleDialog implements SelectionChangedListener, MapViewPaintable { public static final Color BGCOLOR_NO_SIG = new Color(234, 234, 234); // public static final Color BGCOLOR_VALID_SIG = new Color(235, 255,177); public static final Color BGCOLOR_VALID_SIG = new Color(74, 245, 106); public static final Color BGCOLOR_BROKEN_SIG = new Color(255, 197, 197); public static final Color BGCOLOR_REMOVED_ITEM = new Color(255, 100, 100); public static final Color BGCOLOR_UPDATED_ITEM = new Color(249, 221, 95); /** The check signatures button */ private final SideButton checkButton; /** The sign button */ private final SideButton signButton; /** The show sigs button */ private final SideButton showButton; private final Map<String, Byte> rowStatus = new HashMap<>(); /** The selected osmData */ private Collection<? extends OsmPrimitive> osmData; private final List<WaySegment> selectedSegments = new ArrayList<>(); private final List<OsmPrimitive> selectedPrimitives = new ArrayList<>(); /** The JTree for showing the geometry */ private final JTree geomTree; /** * The property data. */ private final DefaultTableModel propertyData = new DefaultTableModel() { @Override public boolean isCellEditable(int row, int column) { return false; } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } }; private final JTable propertyTable = new JTable(propertyData) { @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component c = super.prepareRenderer(renderer, row, column); Byte stat = rowStatus.get(getModel().getValueAt(row, 0)); if (!isRowSelected(row)) switch (stat.byteValue()) { case -2: c.setBackground(BGCOLOR_REMOVED_ITEM); break; case -1: c.setBackground(BGCOLOR_BROKEN_SIG); break; case 1: c.setBackground(BGCOLOR_VALID_SIG); break; default: c.setBackground(BGCOLOR_NO_SIG); break; } return c; } }; /** The JTable for members of a relation */ /*private final DefaultTableModel memberData = new DefaultTableModel() { @Override public boolean isCellEditable(int row, int column) { return false; } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } }; private final JTable memberTable = new JTable(memberData) { @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component c = super.prepareRenderer(renderer, row, column); Byte stat = rowStatus.get(getModel().getValueAt(row, 0)); if (!isRowSelected(row)) switch (stat.byteValue()) { case -2: c.setBackground( BGCOLOR_REMOVED_ITEM); break; case -1: c.setBackground( BGCOLOR_BROKEN_SIG); break; case 1: c.setBackground( BGCOLOR_VALID_SIG); break; default: c.setBackground( BGCOLOR_NO_SIG); break; } return c; } };*/ /** * Constructor */ public TrustDialog() { super(tr("Object signatures"), "trustosm", tr("Open object signing window."), Shortcut.registerShortcut("subwindow:trustosm", tr("Toggle: {0}", tr("Object signatures")), KeyEvent.VK_T, Shortcut.ALT_CTRL), 150); Main.map.mapView.addTemporaryLayer(this); // setting up the properties table propertyData.setColumnIdentifiers(new String[]{tr("Key"), tr("Value")}); // copy and paste from org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() { /** * */ private static final long serialVersionUID = 8003207668070727861L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column); if (c instanceof JLabel) { String str = null; if (value instanceof String) { str = (String) value; } else if (value instanceof Map<?, ?>) { Map<?, ?> v = (Map<?, ?>) value; if (v.size() != 1) { str = tr("<different>"); c.setFont(c.getFont().deriveFont(Font.ITALIC)); } else { final Map.Entry<?, ?> entry = v.entrySet().iterator().next(); str = (String) entry.getKey(); } } ((JLabel) c).setText(str); } return c; } }); geomTree = new JTree(createTree()); geomTree.setBackground(BGCOLOR_NO_SIG); geomTree.setRootVisible(false); geomTree.setCellRenderer(new DefaultTreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; if (node.isRoot()) return this; setBackgroundNonSelectionColor(BGCOLOR_NO_SIG); Object o = node.getUserObject(); if (o instanceof OsmPrimitive) { OsmPrimitive osm = (OsmPrimitive) o; setIcon(ImageProvider.get(OsmPrimitiveType.from(osm))); setText(osm.getDisplayName(DefaultNameFormatter.getInstance())); if (osm instanceof Node) { TrustSignatures sigs; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (TrustOSMplugin.signedItems.containsKey(id) && (sigs = ((TrustNode) TrustOSMplugin.signedItems.get(id)).getNodeSigs()) != null) { byte stat = sigs.getStatus(); switch (stat) { case -2: setBackgroundNonSelectionColor(BGCOLOR_REMOVED_ITEM); break; case -1: setBackgroundNonSelectionColor(BGCOLOR_BROKEN_SIG); break; case 1: setBackgroundNonSelectionColor(BGCOLOR_VALID_SIG); break; default: setBackgroundNonSelectionColor(BGCOLOR_NO_SIG); break; } } } } else if (o instanceof WaySegment) { WaySegment seg = (WaySegment) o; setIcon(ImageProvider.get("mapmode/addsegment")); setText(seg.getFirstNode().getDisplayName(DefaultNameFormatter.getInstance()) + " ----- " + seg.getSecondNode().getDisplayName(DefaultNameFormatter.getInstance())); TrustSignatures sigs; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(seg.way); if (TrustOSMplugin.signedItems.containsKey(id) && (sigs = ((TrustWay) TrustOSMplugin.signedItems.get(id)).getSigsOnSegment(seg)) != null) { switch (sigs.getStatus()) { case -2: setBackgroundNonSelectionColor(BGCOLOR_REMOVED_ITEM); break; case -1: setBackgroundNonSelectionColor(BGCOLOR_BROKEN_SIG); break; case 1: setBackgroundNonSelectionColor(BGCOLOR_VALID_SIG); break; default: setBackgroundNonSelectionColor(BGCOLOR_NO_SIG); break; } } } return this; } }); geomTree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { // unhighlight everything for (OsmPrimitive p : selectedPrimitives) { p.setHighlighted(false); } selectedPrimitives.clear(); selectedSegments.clear(); if (geomTree.getSelectionPaths() != null) for (TreePath tp : geomTree.getSelectionPaths()) { Object o = ((DefaultMutableTreeNode) tp.getLastPathComponent()).getUserObject(); if (o instanceof WaySegment) { selectedSegments.add((WaySegment) o); } else if (o instanceof OsmPrimitive) { OsmPrimitive highlight = (OsmPrimitive) o; highlight.setHighlighted(true); selectedPrimitives.add(highlight); } } Main.map.mapView.repaint(); } }); propertyTable.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent fe) { geomTree.clearSelection(); } @Override public void focusLost(FocusEvent fe) { } }); geomTree.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent fe) { propertyTable.clearSelection(); } @Override public void focusLost(FocusEvent fe) { } }); JPanel dataPanel = new JPanel(); dataPanel.setLayout(new BoxLayout(dataPanel, BoxLayout.PAGE_AXIS)); propertyTable.setAlignmentX(LEFT_ALIGNMENT); dataPanel.add(propertyTable); geomTree.setAlignmentX(LEFT_ALIGNMENT); dataPanel.add(geomTree); checkButton = new SideButton(new CheckAction()); signButton = new SideButton(new SignAction()); showButton = new SideButton(new ShowAction()); createLayout(dataPanel, true, Arrays.asList(new SideButton[] { checkButton, signButton, showButton })); DataSet.addSelectionListener(this); } private class CheckAction extends JosmAction { CheckAction() { super(tr("Check"), "checksignatures", tr("Check all available signatures for selected object."), null, false); } @Override public void actionPerformed(ActionEvent e) { if (osmData != null) { for (OsmPrimitive osm : osmData) { String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (TrustOSMplugin.signedItems.containsKey(id)) TrustAnalyzer.checkEverything(TrustOSMplugin.signedItems.get(id)); } } updateTable(); geomTree.repaint(); } } private class SignAction extends JosmAction { SignAction() { super(tr("Sign"), "sign", tr("Digital sign selected Tags, if you believe they are correct."), null, false); } @Override public void actionPerformed(ActionEvent e) { if (osmData != null) { for (int i : propertyTable.getSelectedRows()) { String key = (String) propertyTable.getValueAt(i, 0); for (OsmPrimitive osm : osmData) { if (osm.keySet().contains(key)) { String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); TrustOsmPrimitive trust = TrustOSMplugin.signedItems.containsKey(id) ? TrustOSMplugin.signedItems.get(id) : TrustOsmPrimitive.createTrustOsmPrimitive(osm); if (TrustOSMplugin.gpg.signTag(trust, key)) TrustOSMplugin.signedItems.put(id, trust); } } } } if (geomTree.getSelectionPaths() != null) for (TreePath tp : geomTree.getSelectionPaths()) { Object o = ((DefaultMutableTreeNode) tp.getLastPathComponent()).getUserObject(); if (o instanceof OsmPrimitive) { OsmPrimitive osm = (OsmPrimitive) o; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (osm instanceof Node) { Node osmNode = ((Node) osm); TrustNode trust = TrustOSMplugin.signedItems.containsKey(id) ? (TrustNode) TrustOSMplugin.signedItems.get(id) : new TrustNode(osmNode); trust.storeNodeSig(TrustOSMplugin.gpg.signNode(osmNode)); TrustOSMplugin.signedItems.put(id, trust); /* TreePath parentPath = tp.getParentPath(); if (geomTree.isPathSelected(parentPath)) return; Node osmNode = ((Node) osm); if (((DefaultMutableTreeNode) parentPath.getLastPathComponent()).getUserObject() instanceof Way) { osm = (OsmPrimitive) ((DefaultMutableTreeNode) parentPath.getLastPathComponent()).getUserObject(); id = String.valueOf(osm.getUniqueId()); } TrustOsmPrimitive trust = TrustOSMplugin.signedItems.containsKey(id) ? TrustOSMplugin.signedItems.get(id) : TrustOsmPrimitive.createTrustOsmPrimitive(osm); trust.storeNodeSig(osmNode, TrustOSMplugin.gpg.signNode(osm,osmNode)); TrustOSMplugin.signedItems.put(id, trust); */ } else if (osm instanceof Way) { TrustOSMplugin.signedItems.put(id, TrustOSMplugin.gpg.signWay(TrustOSMplugin.signedItems.containsKey(id) ? (TrustWay) TrustOSMplugin.signedItems.get(id) : new TrustWay(osm))); /*Way osmWay = ((Way) osm); TrustWay trust = TrustOSMplugin.signedItems.containsKey(id) ? (TrustWay) TrustOSMplugin.signedItems.get(id) : new TrustWay(osmWay); trust.storeSegmentSig(TrustOSMplugin.gpg.signWay(osmWay)); TrustOSMplugin.signedItems.put(id, trust); */ } } else if (o instanceof WaySegment) { TreePath parentPath = tp.getParentPath(); if (geomTree.isPathSelected(parentPath)) return; WaySegment seg = (WaySegment) o; List<Node> nodes = new ArrayList<>(); nodes.add(seg.getFirstNode()); nodes.add(seg.getSecondNode()); Way w = seg.way; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(w); TrustWay trust = TrustOSMplugin.signedItems.containsKey(id) ? (TrustWay) TrustOSMplugin.signedItems.get(id) : new TrustWay(w); trust.storeSegmentSig(nodes, TrustOSMplugin.gpg.signSegment(trust, nodes)); TrustOSMplugin.signedItems.put(id, trust); } } updateTable(); geomTree.repaint(); } } private class ShowAction extends JosmAction { ShowAction() { super(tr("Show"), "showsig", tr("Show all available signatures for selected attribute."), null, false); } @Override public void actionPerformed(ActionEvent e) { if (osmData != null) { for (int i : propertyTable.getSelectedRows()) { String key = (String) propertyTable.getValueAt(i, 0); for (OsmPrimitive osm : osmData) { String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (osm.keySet().contains(key) && TrustOSMplugin.signedItems.containsKey(id)) { TrustSignaturesDialog.showSignaturesDialog(TrustOSMplugin.signedItems.get(id), key); } } } } if (geomTree.getSelectionPaths() != null) { for (TreePath tp : geomTree.getSelectionPaths()) { Object o = ((DefaultMutableTreeNode) tp.getLastPathComponent()).getUserObject(); if (o instanceof OsmPrimitive) { OsmPrimitive osm = (OsmPrimitive) o; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (osm instanceof Node) { if (TrustOSMplugin.signedItems.containsKey(id)) { TrustSignaturesDialog.showSignaturesDialog((TrustNode) TrustOSMplugin.signedItems.get(id)); } } /*else if (osm instanceof Way) { TrustOSMplugin.signedItems.put(id, TrustOSMplugin.gpg.signGeometry( TrustOSMplugin.signedItems.containsKey(id)? TrustOSMplugin.signedItems.get(id) : new TrustOSMItem(osm))); }*/ } else if (o instanceof WaySegment) { WaySegment seg = (WaySegment) o; String id = TrustOsmPrimitive.createUniqueObjectIdentifier(seg.way); if (TrustOSMplugin.signedItems.containsKey(id)) { List<Node> nodes = new ArrayList<>(); nodes.add(seg.getFirstNode()); nodes.add(seg.getSecondNode()); TrustSignaturesDialog.showSignaturesDialog((TrustWay) TrustOSMplugin.signedItems.get(id), nodes); } } } } } } /* public void showSignaturesDialog(TrustOSMItem trust, String key) { TrustSignatures sigs; if ((sigs = trust.getSigsOnKey(key)) == null) { JOptionPane.showMessageDialog(null, tr("Sorry, there are no Signatures for the selected Attribute."), tr("No Signature found"), JOptionPane.WARNING_MESSAGE); } else { JPanel p = new JPanel(); p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS)); Dimension d = new Dimension(0, 20); JLabel head = new JLabel(tr("Selected key value pair was:\n{0}={1}",key,trust.getOsmItem().get(key))); head.setAlignmentX(LEFT_ALIGNMENT); p.add(head); p.add(Box.createRigidArea(d)); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd:hh.mm.ss"); for (PGPSignature s : sigs.getSignatures()) { JTextArea sigtext = new JTextArea(sigs.getArmoredFulltextSignature(s)); sigtext.setEditable(false); sigtext.setAlignmentX(LEFT_ALIGNMENT); p.add(sigtext); JLabel siginfo = new JLabel(tr("Signature created at {0} by User {1}", formatter.format(s.getCreationTime()), s.getHashedSubPackets().getSignerUserID())); siginfo.setAlignmentX(LEFT_ALIGNMENT); p.add(siginfo); p.add(Box.createRigidArea(d)); } JScrollPane scroller = new JScrollPane(p); JPanel content = new JPanel(); content.setMaximumSize(new Dimension(600, 500)); content.add(scroller); JOptionPane.showMessageDialog(Main.parent,content, tr("Clearsigned Signature"), JOptionPane.PLAIN_MESSAGE); } } */ public static List<WaySegment> generateSegmentListFromWay(Way w) { List<WaySegment> segList = new ArrayList<>(); for (int i = 0; i < w.getNodesCount()-1; i++) { segList.add(new WaySegment(w, i)); } return segList; } private DefaultTreeModel createTree() { DefaultMutableTreeNode root = new DefaultMutableTreeNode(); DefaultMutableTreeNode wayNode; if (osmData != null) for (OsmPrimitive osm : osmData) { //String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (osm instanceof Node) { root.add(new DefaultMutableTreeNode(osm)); } else if (osm instanceof Way) { wayNode = new DefaultMutableTreeNode(osm); List<WaySegment> presentSegments = TrustDialog.generateSegmentListFromWay(((Way) osm)); for (WaySegment seg : presentSegments) { wayNode.add(new DefaultMutableTreeNode(seg)); } /* wayNode = new DefaultMutableTreeNode(osm); List<Node> presentNodes = ((Way)osm).getNodes(); Iterator<Node> iter = presentNodes.iterator(); while (iter.hasNext()) { wayNode.add(new DefaultMutableTreeNode(iter.next())); } if (TrustOSMplugin.signedItems.containsKey(id)) { TrustOsmPrimitive trust = TrustOSMplugin.signedItems.get(id); HashSet<Node> signedNodes = new HashSet<Node>(trust.getGeomSigs().keySet()); signedNodes.removeAll(presentNodes); iter = signedNodes.iterator(); Node removedNode; while (iter.hasNext()) { removedNode = iter.next(); trust.updateNodeSigStatus(removedNode, TrustSignatures.ITEM_REMOVED); wayNode.add(new DefaultMutableTreeNode(removedNode)); } } */ root.add(wayNode); } else if (osm instanceof Relation) { } } return new DefaultTreeModel(root); } public void updateTable() { // re-load property data propertyData.setRowCount(0); Map<String, Map<String, Integer>> valueCount = new TreeMap<>(); TrustOsmPrimitive trust; valueCount.clear(); rowStatus.clear(); boolean sigsAvailable = false; if (osmData != null) { for (OsmPrimitive osm : osmData) { String id = TrustOsmPrimitive.createUniqueObjectIdentifier(osm); if (TrustOSMplugin.signedItems.containsKey(id)) { trust = TrustOSMplugin.signedItems.get(id); sigsAvailable = true; /* Map<String,String> tags = osm.getKeys(); Map<String, TrustSignatures> signedTags = trust.getTagSigs(); HashSet<String> removedKeys = new HashSet<String>(signedTags.keySet()); removedKeys.removeAll(tags.keySet()); for (String removedKey: removedKeys) { TrustSignatures sigs = signedTags.get(removedKey); sigs.setStatus( TrustSignatures.ITEM_REMOVED); String[] kv = TrustOsmPrimitive.generateTagsFromSigtext(sigs.getOnePlainText()); tags.put(kv[0],kv[1]); } */ } else { trust = TrustOsmPrimitive.createTrustOsmPrimitive(osm); sigsAvailable = false; } for (String key: osm.keySet()) { String value = osm.get(key); byte status = sigsAvailable && trust.getTagSigs().containsKey(key) ? trust.getTagSigs().get(key).getStatus() : TrustSignatures.SIG_UNKNOWN; Byte oldstatus = rowStatus.containsKey(key) ? rowStatus.get(key) : Byte.valueOf(TrustSignatures.SIG_VALID); Byte sigstatus = Byte.valueOf(status); Byte newstatus; if (sigstatus.equals(Byte.valueOf(TrustSignatures.SIG_BROKEN)) || oldstatus.equals(Byte.valueOf(TrustSignatures.SIG_BROKEN))) { newstatus = Byte.valueOf(TrustSignatures.SIG_BROKEN); } else if (sigstatus.equals(Byte.valueOf(TrustSignatures.SIG_UNKNOWN)) || oldstatus.equals(Byte.valueOf(TrustSignatures.SIG_UNKNOWN))) { newstatus = Byte.valueOf(TrustSignatures.SIG_UNKNOWN); } else newstatus = Byte.valueOf(TrustSignatures.SIG_VALID); rowStatus.put(key, newstatus); if (valueCount.containsKey(key)) { Map<String, Integer> v = valueCount.get(key); v.put(value, v.containsKey(value) ? v.get(value) + 1 : 1); } else { TreeMap<String, Integer> v = new TreeMap<>(); v.put(value, 1); valueCount.put(key, v); } } } for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) { int count = 0; for (Entry<String, Integer> e1: e.getValue().entrySet()) { count += e1.getValue(); } if (count < osmData.size()) { e.getValue().put("", osmData.size()-count); } propertyData.addRow(new Object[]{e.getKey(), e.getValue()}); } } boolean hasSelection = osmData != null && !osmData.isEmpty(); boolean hasTags = hasSelection && propertyData.getRowCount() > 0; propertyTable.setVisible(hasTags); propertyTable.getTableHeader().setVisible(hasTags); } @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { this.osmData = newSelection; if (!isVisible()) return; geomTree.setModel(createTree()); updateTable(); } @Override public void paint(Graphics2D g, MapView mv, Bounds bbox) { // if there are no Segments to highlight - return if (selectedSegments.isEmpty()) return; // sanity checks if (Main.map.mapView == null) return; Graphics2D g2 = g; g2.setColor(PaintColors.HIGHLIGHT.get()); g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // highlight all selected WaySegments for (WaySegment seg : selectedSegments) { GeneralPath b = new GeneralPath(); Point p1 = mv.getPoint(seg.getFirstNode()); Point p2 = mv.getPoint(seg.getSecondNode()); b.moveTo(p1.x, p1.y); b.lineTo(p2.x, p2.y); g2.draw(b); } g2.setStroke(new BasicStroke(1)); } }