001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.dialogs.relation;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.Container;
007 import java.awt.Dimension;
008 import java.awt.event.ActionEvent;
009 import java.awt.event.KeyEvent;
010 import java.util.Arrays;
011 import java.util.Collection;
012
013 import javax.swing.AbstractAction;
014 import javax.swing.JComponent;
015 import javax.swing.JPopupMenu;
016 import javax.swing.JTable;
017 import javax.swing.JViewport;
018 import javax.swing.KeyStroke;
019 import javax.swing.ListSelectionModel;
020 import javax.swing.event.ListSelectionEvent;
021 import javax.swing.event.ListSelectionListener;
022
023 import org.openstreetmap.josm.Main;
024 import org.openstreetmap.josm.actions.AutoScaleAction;
025 import org.openstreetmap.josm.actions.ZoomToAction;
026 import org.openstreetmap.josm.data.osm.Way;
027 import org.openstreetmap.josm.gui.MapView;
028 import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
029 import org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction;
030 import org.openstreetmap.josm.gui.layer.Layer;
031 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
032 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTable;
033
034 public class MemberTable extends OsmPrimitivesTable implements IMemberModelListener {
035
036 /** the additional actions in popup menu */
037 private ZoomToGapAction zoomToGap;
038
039 /**
040 * constructor
041 *
042 * @param model
043 * @param columnModel
044 */
045 public MemberTable(OsmDataLayer layer, MemberTableModel model) {
046 super(model, new MemberTableColumnModel(layer.data), model.getSelectionModel());
047 setLayer(layer);
048 model.addMemberModelListener(this);
049 init();
050 }
051
052 /**
053 * initialize the table
054 */
055 protected void init() {
056 MemberRoleCellEditor ce = (MemberRoleCellEditor)getColumnModel().getColumn(0).getCellEditor();
057 setRowHeight(ce.getEditor().getPreferredSize().height);
058 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
059 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
060 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
061
062 // make ENTER behave like TAB
063 //
064 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
065 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
066
067 // install custom navigation actions
068 //
069 getActionMap().put("selectNextColumnCell", new SelectNextColumnCellAction());
070 getActionMap().put("selectPreviousColumnCell", new SelectPreviousColumnCellAction());
071 }
072
073 @Override
074 protected ZoomToAction buildZoomToAction() {
075 return new ZoomToAction(this);
076 }
077
078 @Override
079 protected JPopupMenu buildPopupMenu() {
080 JPopupMenu menu = super.buildPopupMenu();
081 zoomToGap = new ZoomToGapAction();
082 MapView.addLayerChangeListener(zoomToGap);
083 getSelectionModel().addListSelectionListener(zoomToGap);
084 menu.add(zoomToGap);
085 menu.addSeparator();
086 menu.add(new SelectPreviousGapAction());
087 menu.add(new SelectNextGapAction());
088 return menu;
089 }
090
091 @Override
092 public Dimension getPreferredSize(){
093 Container c = getParent();
094 while(c != null && ! (c instanceof JViewport)) {
095 c = c.getParent();
096 }
097 if (c != null) {
098 Dimension d = super.getPreferredSize();
099 d.width = c.getSize().width;
100 return d;
101 }
102 return super.getPreferredSize();
103 }
104
105 public void makeMemberVisible(int index) {
106 scrollRectToVisible(getCellRect(index, 0, true));
107 }
108
109 /**
110 * Action to be run when the user navigates to the next cell in the table, for instance by
111 * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
112 * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
113 * when the user leaves the last cell in the table</li> <ul>
114 *
115 *
116 */
117 class SelectNextColumnCellAction extends AbstractAction {
118 public void actionPerformed(ActionEvent e) {
119 run();
120 }
121
122 public void run() {
123 int col = getSelectedColumn();
124 int row = getSelectedRow();
125 if (getCellEditor() != null) {
126 getCellEditor().stopCellEditing();
127 }
128
129 if (col == 0 && row < getRowCount() - 1) {
130 row++;
131 } else if (row < getRowCount() - 1) {
132 col = 0;
133 row++;
134 }
135 changeSelection(row, col, false, false);
136 }
137 }
138
139 /**
140 * Action to be run when the user navigates to the previous cell in the table, for instance by
141 * pressing Shift-TAB
142 *
143 */
144 private class SelectPreviousColumnCellAction extends AbstractAction {
145
146 public void actionPerformed(ActionEvent e) {
147 int col = getSelectedColumn();
148 int row = getSelectedRow();
149 if (getCellEditor() != null) {
150 getCellEditor().stopCellEditing();
151 }
152
153 if (col <= 0 && row <= 0) {
154 // change nothing
155 } else if (row > 0) {
156 col = 0;
157 row--;
158 }
159 changeSelection(row, col, false, false);
160 }
161 }
162
163 @Override
164 public void unlinkAsListener() {
165 super.unlinkAsListener();
166 MapView.removeLayerChangeListener(zoomToGap);
167 }
168
169 private class SelectPreviousGapAction extends AbstractAction {
170
171 public SelectPreviousGapAction() {
172 putValue(NAME, tr("Select previous Gap"));
173 putValue(SHORT_DESCRIPTION, tr("Select the previous relation member which gives rise to a gap"));
174 }
175
176 @Override
177 public void actionPerformed(ActionEvent e) {
178 int i = getSelectedRow() - 1;
179 while (i >= 0 && getMemberTableModel().getWayConnection(i).linkPrev) {
180 i--;
181 }
182 if (i >= 0) {
183 getSelectionModel().setSelectionInterval(i, i);
184 }
185 }
186 }
187
188 private class SelectNextGapAction extends AbstractAction {
189
190 public SelectNextGapAction() {
191 putValue(NAME, tr("Select next Gap"));
192 putValue(SHORT_DESCRIPTION, tr("Select the next relation member which gives rise to a gap"));
193 }
194
195 @Override
196 public void actionPerformed(ActionEvent e) {
197 int i = getSelectedRow() + 1;
198 while (i < getRowCount() && getMemberTableModel().getWayConnection(i).linkNext) {
199 i++;
200 }
201 if (i < getRowCount()) {
202 getSelectionModel().setSelectionInterval(i, i);
203 }
204 }
205 }
206
207 private class ZoomToGapAction extends AbstractAction implements LayerChangeListener, ListSelectionListener {
208
209 public ZoomToGapAction() {
210 putValue(NAME, tr("Zoom to Gap"));
211 putValue(SHORT_DESCRIPTION, tr("Zoom to the gap in the way sequence"));
212 updateEnabledState();
213 }
214
215 private WayConnectionType getConnectionType() {
216 return getMemberTableModel().getWayConnection(getSelectedRows()[0]);
217 }
218
219 private final Collection<Direction> connectionTypesOfInterest = Arrays.asList(WayConnectionType.Direction.FORWARD, WayConnectionType.Direction.BACKWARD);
220
221 private boolean hasGap() {
222 WayConnectionType connectionType = getConnectionType();
223 return connectionTypesOfInterest.contains(connectionType.direction)
224 && !(connectionType.linkNext && connectionType.linkPrev);
225 }
226
227 @Override
228 public void actionPerformed(ActionEvent e) {
229 WayConnectionType connectionType = getConnectionType();
230 Way way = (Way) getMemberTableModel().getReferredPrimitive(getSelectedRows()[0]);
231 if (!connectionType.linkPrev) {
232 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction)
233 ? way.firstNode() : way.lastNode());
234 AutoScaleAction.autoScale("selection");
235 } else if (!connectionType.linkNext) {
236 getLayer().data.setSelected(WayConnectionType.Direction.FORWARD.equals(connectionType.direction)
237 ? way.lastNode() : way.firstNode());
238 AutoScaleAction.autoScale("selection");
239 }
240 }
241
242 private void updateEnabledState() {
243 setEnabled(Main.main != null
244 && Main.main.getEditLayer() == getLayer()
245 && getSelectedRowCount() == 1
246 && hasGap());
247 }
248
249 @Override
250 public void valueChanged(ListSelectionEvent e) {
251 updateEnabledState();
252 }
253
254 @Override
255 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
256 updateEnabledState();
257 }
258
259 @Override
260 public void layerAdded(Layer newLayer) {
261 updateEnabledState();
262 }
263
264 @Override
265 public void layerRemoved(Layer oldLayer) {
266 updateEnabledState();
267 }
268 }
269
270 protected MemberTableModel getMemberTableModel() {
271 return (MemberTableModel) getModel();
272 }
273 }