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    }