001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Dimension; 007import java.awt.GraphicsEnvironment; 008import java.awt.event.ActionEvent; 009import java.util.ArrayList; 010import java.util.Arrays; 011import java.util.Collection; 012import java.util.List; 013 014import javax.swing.AbstractAction; 015import javax.swing.DropMode; 016import javax.swing.JPopupMenu; 017import javax.swing.JTable; 018import javax.swing.ListSelectionModel; 019import javax.swing.SwingUtilities; 020import javax.swing.event.ListSelectionEvent; 021import javax.swing.event.ListSelectionListener; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.actions.AutoScaleAction; 025import org.openstreetmap.josm.actions.ZoomToAction; 026import org.openstreetmap.josm.data.osm.OsmPrimitive; 027import org.openstreetmap.josm.data.osm.Relation; 028import org.openstreetmap.josm.data.osm.RelationMember; 029import org.openstreetmap.josm.data.osm.Way; 030import org.openstreetmap.josm.gui.MapView; 031import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 032import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; 033import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction; 034import org.openstreetmap.josm.gui.layer.Layer; 035import org.openstreetmap.josm.gui.layer.OsmDataLayer; 036import org.openstreetmap.josm.gui.util.HighlightHelper; 037import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable; 038 039public class MemberTable extends OsmPrimitivesTable implements IMemberModelListener { 040 041 /** the additional actions in popup menu */ 042 private ZoomToGapAction zoomToGap; 043 private final transient HighlightHelper highlightHelper = new HighlightHelper(); 044 private boolean highlightEnabled; 045 046 /** 047 * constructor for relation member table 048 * 049 * @param layer the data layer of the relation. Must not be null 050 * @param relation the relation. Can be null 051 * @param model the table model 052 */ 053 public MemberTable(OsmDataLayer layer, Relation relation, MemberTableModel model) { 054 super(model, new MemberTableColumnModel(layer.data, relation), model.getSelectionModel()); 055 setLayer(layer); 056 model.addMemberModelListener(this); 057 058 MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor(); 059 setRowHeight(ce.getEditor().getPreferredSize().height); 060 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); 061 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 062 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 063 064 installCustomNavigation(0); 065 initHighlighting(); 066 067 if (!GraphicsEnvironment.isHeadless()) { 068 setTransferHandler(new MemberTransferHandler()); 069 setFillsViewportHeight(true); // allow drop on empty table 070 setDragEnabled(true); 071 setDropMode(DropMode.INSERT_ROWS); 072 } 073 } 074 075 @Override 076 protected ZoomToAction buildZoomToAction() { 077 return new ZoomToAction(this); 078 } 079 080 @Override 081 protected JPopupMenu buildPopupMenu() { 082 JPopupMenu menu = super.buildPopupMenu(); 083 zoomToGap = new ZoomToGapAction(); 084 MapView.addLayerChangeListener(zoomToGap); 085 getSelectionModel().addListSelectionListener(zoomToGap); 086 menu.add(zoomToGap); 087 menu.addSeparator(); 088 menu.add(new SelectPreviousGapAction()); 089 menu.add(new SelectNextGapAction()); 090 return menu; 091 } 092 093 @Override 094 public Dimension getPreferredSize() { 095 return getPreferredFullWidthSize(); 096 } 097 098 @Override 099 public void makeMemberVisible(int index) { 100 scrollRectToVisible(getCellRect(index, 0, true)); 101 } 102 103 private transient ListSelectionListener highlighterListener = new ListSelectionListener() { 104 @Override 105 public void valueChanged(ListSelectionEvent lse) { 106 if (Main.isDisplayingMapView()) { 107 Collection<RelationMember> sel = getMemberTableModel().getSelectedMembers(); 108 final List<OsmPrimitive> toHighlight = new ArrayList<>(); 109 for (RelationMember r: sel) { 110 if (r.getMember().isUsable()) { 111 toHighlight.add(r.getMember()); 112 } 113 } 114 SwingUtilities.invokeLater(new Runnable() { 115 @Override 116 public void run() { 117 if (Main.isDisplayingMapView() && highlightHelper.highlightOnly(toHighlight)) { 118 Main.map.mapView.repaint(); 119 } 120 } 121 }); 122 } 123 } 124 }; 125 126 private void initHighlighting() { 127 highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true); 128 if (!highlightEnabled) return; 129 getMemberTableModel().getSelectionModel().addListSelectionListener(highlighterListener); 130 if (Main.isDisplayingMapView()) { 131 HighlightHelper.clearAllHighlighted(); 132 Main.map.mapView.repaint(); 133 } 134 } 135 136 @Override 137 public void unlinkAsListener() { 138 super.unlinkAsListener(); 139 MapView.removeLayerChangeListener(zoomToGap); 140 } 141 142 public void stopHighlighting() { 143 if (highlighterListener == null) return; 144 if (!highlightEnabled) return; 145 getMemberTableModel().getSelectionModel().removeListSelectionListener(highlighterListener); 146 highlighterListener = null; 147 if (Main.isDisplayingMapView()) { 148 HighlightHelper.clearAllHighlighted(); 149 Main.map.mapView.repaint(); 150 } 151 } 152 153 private class SelectPreviousGapAction extends AbstractAction { 154 155 SelectPreviousGapAction() { 156 putValue(NAME, tr("Select previous Gap")); 157 putValue(SHORT_DESCRIPTION, tr("Select the previous relation member which gives rise to a gap")); 158 } 159 160 @Override 161 public void actionPerformed(ActionEvent e) { 162 int i = getSelectedRow() - 1; 163 while (i >= 0 && getMemberTableModel().getWayConnection(i).linkPrev) { 164 i--; 165 } 166 if (i >= 0) { 167 getSelectionModel().setSelectionInterval(i, i); 168 } 169 } 170 } 171 172 private class SelectNextGapAction extends AbstractAction { 173 174 SelectNextGapAction() { 175 putValue(NAME, tr("Select next Gap")); 176 putValue(SHORT_DESCRIPTION, tr("Select the next relation member which gives rise to a gap")); 177 } 178 179 @Override 180 public void actionPerformed(ActionEvent e) { 181 int i = getSelectedRow() + 1; 182 while (i < getRowCount() && getMemberTableModel().getWayConnection(i).linkNext) { 183 i++; 184 } 185 if (i < getRowCount()) { 186 getSelectionModel().setSelectionInterval(i, i); 187 } 188 } 189 } 190 191 private class ZoomToGapAction extends AbstractAction implements LayerChangeListener, ListSelectionListener { 192 193 /** 194 * Constructs a new {@code ZoomToGapAction}. 195 */ 196 ZoomToGapAction() { 197 putValue(NAME, tr("Zoom to Gap")); 198 putValue(SHORT_DESCRIPTION, tr("Zoom to the gap in the way sequence")); 199 updateEnabledState(); 200 } 201 202 private WayConnectionType getConnectionType() { 203 return getMemberTableModel().getWayConnection(getSelectedRows()[0]); 204 } 205 206 private final Collection<Direction> connectionTypesOfInterest = Arrays.asList( 207 WayConnectionType.Direction.FORWARD, WayConnectionType.Direction.BACKWARD); 208 209 private boolean hasGap() { 210 WayConnectionType connectionType = getConnectionType(); 211 return connectionTypesOfInterest.contains(connectionType.direction) 212 && !(connectionType.linkNext && connectionType.linkPrev); 213 } 214 215 @Override 216 public void actionPerformed(ActionEvent e) { 217 WayConnectionType connectionType = getConnectionType(); 218 Way way = (Way) getMemberTableModel().getReferredPrimitive(getSelectedRows()[0]); 219 if (!connectionType.linkPrev) { 220 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction) 221 ? way.firstNode() : way.lastNode()); 222 AutoScaleAction.autoScale("selection"); 223 } else if (!connectionType.linkNext) { 224 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction) 225 ? way.lastNode() : way.firstNode()); 226 AutoScaleAction.autoScale("selection"); 227 } 228 } 229 230 private void updateEnabledState() { 231 setEnabled(Main.main != null 232 && Main.main.getEditLayer() == getLayer() 233 && getSelectedRowCount() == 1 234 && hasGap()); 235 } 236 237 @Override 238 public void valueChanged(ListSelectionEvent e) { 239 updateEnabledState(); 240 } 241 242 @Override 243 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 244 updateEnabledState(); 245 } 246 247 @Override 248 public void layerAdded(Layer newLayer) { 249 updateEnabledState(); 250 } 251 252 @Override 253 public void layerRemoved(Layer oldLayer) { 254 updateEnabledState(); 255 } 256 } 257 258 protected MemberTableModel getMemberTableModel() { 259 return (MemberTableModel) getModel(); 260 } 261}