source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTable.java@ 13486

Last change on this file since 13486 was 13176, checked in by Don-vip, 6 years ago

fix #15620 - select previous gap (or next gap) may lead to an invisible selection in the member table (patch by cmuelle8)

  • Property svn:eol-style set to native
File size: 10.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Dimension;
7import java.awt.GraphicsEnvironment;
8import java.awt.event.ActionEvent;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.Set;
13
14import javax.swing.AbstractAction;
15import javax.swing.DropMode;
16import javax.swing.JPopupMenu;
17import javax.swing.JTable;
18import javax.swing.ListSelectionModel;
19import javax.swing.SwingUtilities;
20import javax.swing.event.ListSelectionEvent;
21import javax.swing.event.ListSelectionListener;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.AutoScaleAction;
25import org.openstreetmap.josm.actions.ZoomToAction;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Relation;
28import org.openstreetmap.josm.data.osm.RelationMember;
29import org.openstreetmap.josm.data.osm.Way;
30import org.openstreetmap.josm.gui.MainApplication;
31import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
32import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction;
33import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
34import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
35import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
36import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
37import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
38import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.util.HighlightHelper;
41import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable;
42import org.openstreetmap.josm.spi.preferences.Config;
43
44/**
45 * The table of members a selected relation has.
46 */
47public class MemberTable extends OsmPrimitivesTable implements IMemberModelListener {
48
49 /** the additional actions in popup menu */
50 private ZoomToGapAction zoomToGap;
51 private final transient HighlightHelper highlightHelper = new HighlightHelper();
52 private boolean highlightEnabled;
53
54 /**
55 * constructor for relation member table
56 *
57 * @param layer the data layer of the relation. Must not be null
58 * @param relation the relation. Can be null
59 * @param model the table model
60 */
61 public MemberTable(OsmDataLayer layer, Relation relation, MemberTableModel model) {
62 super(model, new MemberTableColumnModel(layer.data, relation), model.getSelectionModel());
63 setLayer(layer);
64 model.addMemberModelListener(this);
65
66 MemberRoleCellEditor ce = (MemberRoleCellEditor) getColumnModel().getColumn(0).getCellEditor();
67 setRowHeight(ce.getEditor().getPreferredSize().height);
68 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
69 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
70 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
71
72 installCustomNavigation(0);
73 initHighlighting();
74
75 if (!GraphicsEnvironment.isHeadless()) {
76 setTransferHandler(new MemberTransferHandler());
77 setFillsViewportHeight(true); // allow drop on empty table
78 if (!GraphicsEnvironment.isHeadless()) {
79 setDragEnabled(true);
80 }
81 setDropMode(DropMode.INSERT_ROWS);
82 }
83 }
84
85 @Override
86 protected ZoomToAction buildZoomToAction() {
87 return new ZoomToAction(this);
88 }
89
90 @Override
91 protected JPopupMenu buildPopupMenu() {
92 JPopupMenu menu = super.buildPopupMenu();
93 zoomToGap = new ZoomToGapAction();
94 registerListeners();
95 menu.addSeparator();
96 getSelectionModel().addListSelectionListener(zoomToGap);
97 menu.add(zoomToGap);
98 menu.addSeparator();
99 menu.add(new SelectPreviousGapAction());
100 menu.add(new SelectNextGapAction());
101 return menu;
102 }
103
104 @Override
105 public Dimension getPreferredSize() {
106 return getPreferredFullWidthSize();
107 }
108
109 @Override
110 public void makeMemberVisible(int index) {
111 scrollRectToVisible(getCellRect(index, 0, true));
112 }
113
114 private transient ListSelectionListener highlighterListener = lse -> {
115 if (MainApplication.isDisplayingMapView()) {
116 Collection<RelationMember> sel = getMemberTableModel().getSelectedMembers();
117 final Set<OsmPrimitive> toHighlight = new HashSet<>();
118 for (RelationMember r: sel) {
119 if (r.getMember().isUsable()) {
120 toHighlight.add(r.getMember());
121 }
122 }
123 SwingUtilities.invokeLater(() -> {
124 if (MainApplication.isDisplayingMapView() && highlightHelper.highlightOnly(toHighlight)) {
125 MainApplication.getMap().mapView.repaint();
126 }
127 });
128 }
129 };
130
131 private void initHighlighting() {
132 highlightEnabled = Config.getPref().getBoolean("draw.target-highlight", true);
133 if (!highlightEnabled) return;
134 getMemberTableModel().getSelectionModel().addListSelectionListener(highlighterListener);
135 if (MainApplication.isDisplayingMapView()) {
136 HighlightHelper.clearAllHighlighted();
137 MainApplication.getMap().mapView.repaint();
138 }
139 }
140
141 @Override
142 public void registerListeners() {
143 MainApplication.getLayerManager().addLayerChangeListener(zoomToGap);
144 MainApplication.getLayerManager().addActiveLayerChangeListener(zoomToGap);
145 super.registerListeners();
146 }
147
148 @Override
149 public void unregisterListeners() {
150 super.unregisterListeners();
151 MainApplication.getLayerManager().removeLayerChangeListener(zoomToGap);
152 MainApplication.getLayerManager().removeActiveLayerChangeListener(zoomToGap);
153 }
154
155 public void stopHighlighting() {
156 if (highlighterListener == null) return;
157 if (!highlightEnabled) return;
158 getMemberTableModel().getSelectionModel().removeListSelectionListener(highlighterListener);
159 highlighterListener = null;
160 if (MainApplication.isDisplayingMapView()) {
161 HighlightHelper.clearAllHighlighted();
162 MainApplication.getMap().mapView.repaint();
163 }
164 }
165
166 private class SelectPreviousGapAction extends AbstractAction {
167
168 SelectPreviousGapAction() {
169 putValue(NAME, tr("Select previous Gap"));
170 putValue(SHORT_DESCRIPTION, tr("Select the previous relation member which gives rise to a gap"));
171 }
172
173 @Override
174 public void actionPerformed(ActionEvent e) {
175 int i = getSelectedRow() - 1;
176 while (i >= 0 && getMemberTableModel().getWayConnection(i).linkPrev) {
177 i--;
178 }
179 if (i >= 0) {
180 getSelectionModel().setSelectionInterval(i, i);
181 getMemberTableModel().fireMakeMemberVisible(i);
182 }
183 }
184 }
185
186 private class SelectNextGapAction extends AbstractAction {
187
188 SelectNextGapAction() {
189 putValue(NAME, tr("Select next Gap"));
190 putValue(SHORT_DESCRIPTION, tr("Select the next relation member which gives rise to a gap"));
191 }
192
193 @Override
194 public void actionPerformed(ActionEvent e) {
195 int i = getSelectedRow() + 1;
196 while (i < getRowCount() && getMemberTableModel().getWayConnection(i).linkNext) {
197 i++;
198 }
199 if (i < getRowCount()) {
200 getSelectionModel().setSelectionInterval(i, i);
201 getMemberTableModel().fireMakeMemberVisible(i);
202 }
203 }
204 }
205
206 private class ZoomToGapAction extends AbstractAction implements LayerChangeListener, ActiveLayerChangeListener, ListSelectionListener {
207
208 /**
209 * Constructs a new {@code ZoomToGapAction}.
210 */
211 ZoomToGapAction() {
212 putValue(NAME, tr("Zoom to Gap"));
213 putValue(SHORT_DESCRIPTION, tr("Zoom to the gap in the way sequence"));
214 updateEnabledState();
215 }
216
217 private WayConnectionType getConnectionType() {
218 return getMemberTableModel().getWayConnection(getSelectedRows()[0]);
219 }
220
221 private final Collection<Direction> connectionTypesOfInterest = Arrays.asList(
222 WayConnectionType.Direction.FORWARD, WayConnectionType.Direction.BACKWARD);
223
224 private boolean hasGap() {
225 WayConnectionType connectionType = getConnectionType();
226 return connectionTypesOfInterest.contains(connectionType.direction)
227 && !(connectionType.linkNext && connectionType.linkPrev);
228 }
229
230 @Override
231 public void actionPerformed(ActionEvent e) {
232 WayConnectionType connectionType = getConnectionType();
233 Way way = (Way) getMemberTableModel().getReferredPrimitive(getSelectedRows()[0]);
234 if (!connectionType.linkPrev) {
235 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction)
236 ? way.firstNode() : way.lastNode());
237 AutoScaleAction.autoScale("selection");
238 } else if (!connectionType.linkNext) {
239 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction)
240 ? way.lastNode() : way.firstNode());
241 AutoScaleAction.autoScale("selection");
242 }
243 }
244
245 private void updateEnabledState() {
246 setEnabled(Main.main != null
247 && MainApplication.getLayerManager().getEditLayer() == getLayer()
248 && getSelectedRowCount() == 1
249 && hasGap());
250 }
251
252 @Override
253 public void valueChanged(ListSelectionEvent e) {
254 updateEnabledState();
255 }
256
257 @Override
258 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
259 updateEnabledState();
260 }
261
262 @Override
263 public void layerAdded(LayerAddEvent e) {
264 updateEnabledState();
265 }
266
267 @Override
268 public void layerRemoving(LayerRemoveEvent e) {
269 updateEnabledState();
270 }
271
272 @Override
273 public void layerOrderChanged(LayerOrderChangeEvent e) {
274 // Do nothing
275 }
276 }
277
278 protected MemberTableModel getMemberTableModel() {
279 return (MemberTableModel) getModel();
280 }
281}
Note: See TracBrowser for help on using the repository browser.