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 }