001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.relation;
003
004import java.awt.BasicStroke;
005import java.awt.Color;
006import java.awt.Component;
007import java.awt.Graphics;
008import java.awt.Graphics2D;
009import java.awt.Image;
010
011import javax.swing.JTable;
012
013import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
014import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction;
015import org.openstreetmap.josm.tools.ImageProvider;
016
017/**
018 * This class renders the link column of the member table. It shows if the way segments are connected or not.
019 */
020public class MemberTableLinkedCellRenderer extends MemberTableCellRenderer {
021
022    private static final Image ARROW_UP = ImageProvider.get("dialogs/relation", "arrowup").getImage();
023    private static final Image ARROW_DOWN = ImageProvider.get("dialogs/relation", "arrowdown").getImage();
024    private static final Image CORNERS = ImageProvider.get("dialogs/relation", "roundedcorners").getImage();
025    private static final Image ROUNDABOUT_RIGHT = ImageProvider.get("dialogs/relation", "roundabout_right_tiny").getImage();
026    private static final Image ROUNDABOUT_LEFT = ImageProvider.get("dialogs/relation", "roundabout_left_tiny").getImage();
027    private transient WayConnectionType value = new WayConnectionType();
028
029    @Override
030    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
031            int row, int column) {
032
033        reset();
034        if (value != null) {
035            this.value = (WayConnectionType) value;
036            setToolTipText(((WayConnectionType) value).getTooltip());
037            renderBackgroundForeground(getModel(table), null, isSelected);
038        }
039        return this;
040    }
041
042    @Override
043    public void paintComponent(Graphics g) {
044        super.paintComponent(g);
045        if (value == null || !value.isValid())
046            return;
047
048        int ymax = this.getSize().height - 1;
049        int xloop = 10;
050        int xowloop = 0;
051        if (value.isOnewayLoopForwardPart) {
052            xowloop = -3;
053        }
054        if (value.isOnewayLoopBackwardPart) {
055            xowloop = 3;
056        }
057
058        int xoff = this.getSize().width / 2;
059        if (value.isLoop) {
060            xoff -= xloop / 2 - 1;
061        }
062        int w = 2;
063        int p = 2 + w + 1;
064        int y1 = computePreviousY(g, xloop, xowloop, xoff, w, p);
065        int y2 = computeNextY(g, ymax, xloop, xowloop, xoff, w, p);
066
067        /* vertical lines */
068        if (value.onewayFollowsNext && value.onewayFollowsPrevious) {
069            // foreground is set via renderBackgroundForeground
070            g.setColor(getForeground());
071        } else {
072            g.setColor(getForeground().brighter());
073        }
074        if (value.isLoop) {
075            g.drawLine(xoff+xloop, y1, xoff+xloop, y2);
076        }
077
078        if (value.isOnewayHead) {
079            setDotted(g);
080            y1 = 7;
081
082            int[] xValues = {xoff - xowloop + 1, xoff - xowloop + 1, xoff};
083            int[] yValues = {ymax, y1+1, 1};
084            g.drawPolyline(xValues, yValues, 3);
085            unsetDotted(g);
086            g.drawLine(xoff + xowloop, y1+1, xoff, 1);
087        }
088
089        if (value.isOnewayTail) {
090            setDotted(g);
091            y2 = ymax - 7;
092
093            int[] xValues = {xoff+1, xoff - xowloop + 1, xoff - xowloop + 1};
094            int[] yValues = {ymax-1, y2, y1};
095            g.drawPolyline(xValues, yValues, 3);
096            unsetDotted(g);
097            g.drawLine(xoff + xowloop, y2, xoff, ymax-1);
098        }
099
100        if ((value.isOnewayLoopForwardPart || value.isOnewayLoopBackwardPart) && !value.isOnewayTail && !value.isOnewayHead) {
101            setDotted(g);
102            g.drawLine(xoff - xowloop+1, y1, xoff - xowloop+1, y2 + 1);
103            unsetDotted(g);
104        }
105
106        if (!value.isOnewayLoopForwardPart && !value.isOnewayLoopBackwardPart) {
107            g.drawLine(xoff, y1, xoff, y2);
108        }
109
110        g.drawLine(xoff+xowloop, y1, xoff+xowloop, y2);
111
112        /* special icons */
113        Image arrow = getArrow();
114        if (value.direction == Direction.ROUNDABOUT_LEFT) {
115            g.drawImage(ROUNDABOUT_LEFT, xoff-6, 1, null);
116        } else if (value.direction == Direction.ROUNDABOUT_RIGHT) {
117            g.drawImage(ROUNDABOUT_RIGHT, xoff-6, 1, null);
118        }
119
120        if (!value.isOnewayLoopForwardPart && !value.isOnewayLoopBackwardPart &&
121                (arrow != null)) {
122            g.drawImage(arrow, xoff-3, (y1 + y2) / 2 - 2, null);
123        }
124
125        if (value.isOnewayLoopBackwardPart && value.isOnewayLoopForwardPart) {
126            if (arrow == ARROW_DOWN) {
127                arrow = ARROW_UP;
128            } else if (arrow == ARROW_UP) {
129                arrow = ARROW_DOWN;
130            }
131        }
132
133        if (arrow != null) {
134            g.drawImage(arrow, xoff+xowloop-3, (y1 + y2) / 2 - 2, null);
135        }
136    }
137
138    private int computePreviousY(Graphics g, int xloop, int xowloop, int xoff, int w, int p) {
139        int y1;
140
141        if (value.linkPrev) {
142            if (value.onewayFollowsPrevious) {
143                g.setColor(getForeground());
144            } else {
145                g.setColor(getForeground().brighter());
146            }
147            if (value.isOnewayHead) {
148                g.fillRect(xoff - 1, 0, 3, 1);
149            } else {
150                g.fillRect(xoff - 1 + xowloop, 0, 3, 1);
151            }
152            y1 = 0;
153        } else {
154            if (value.isLoop) {
155                g.setColor(getForeground());
156                y1 = 5;
157                g.drawImage(CORNERS, xoff, y1-3, xoff+3, y1, 0, 0, 3, 3, new Color(0, 0, 0, 0), null);
158                g.drawImage(CORNERS, xoff+xloop-2, y1-3, xoff+xloop+1, y1, 2, 0, 5, 3, new Color(0, 0, 0, 0), null);
159                g.drawLine(xoff+3, y1-3, xoff+xloop-3, y1-3);
160            } else {
161                g.setColor(Color.red);
162                if (value.isOnewayHead) {
163                    g.drawRect(xoff-1, p - 3 - w, w, w);
164                } else {
165                    g.drawRect(xoff-1 + xowloop, p - 1 - w, w, w);
166                }
167                y1 = p;
168            }
169        }
170        return y1;
171    }
172
173    private int computeNextY(Graphics g, int ymax, int xloop, int xowloop, int xoff, int w, int p) {
174        int y2;
175
176        if (value.linkNext) {
177            if (value.onewayFollowsNext) {
178                g.setColor(getForeground());
179            } else {
180                g.setColor(getForeground().brighter());
181            }
182            if (value.isOnewayTail) {
183                g.fillRect(xoff - 1, ymax, 3, 1);
184            } else {
185                g.fillRect(xoff - 1 + xowloop, ymax, 3, 1);
186            }
187            y2 = ymax;
188        } else {
189            if (value.isLoop) {
190                g.setColor(getForeground());
191                y2 = ymax - 5;
192                g.fillRect(xoff-1, y2+2, 3, 3);
193                g.drawLine(xoff, y2, xoff, y2+2);
194                g.drawImage(CORNERS, xoff+xloop-2, y2+1, xoff+xloop+1, y2+4, 2, 2, 5, 5, new Color(0, 0, 0, 0), null);
195                g.drawLine(xoff+3-1, y2+3, xoff+xloop-3, y2+3);
196            } else {
197                g.setColor(Color.red);
198                if (value.isOnewayTail) {
199                    g.drawRect(xoff-1, ymax - p + 3, w, w);
200                } else {
201                    g.drawRect(xoff-1 + xowloop, ymax - p + 1, w, w);
202                }
203                y2 = ymax - p;
204            }
205        }
206        return y2;
207    }
208
209    private Image getArrow() {
210        if (!value.ignoreOneway) {
211            switch (value.direction) {
212            case FORWARD:
213                return ARROW_DOWN;
214            case BACKWARD:
215                return ARROW_UP;
216            default:
217                break;
218            }
219        }
220        return null;
221    }
222
223    private static void setDotted(Graphics g) {
224        ((Graphics2D) g).setStroke(new BasicStroke(
225                1f,
226                BasicStroke.CAP_BUTT,
227                BasicStroke.JOIN_MITER,
228                5f,
229                new float[] {1f, 2f},
230                0f));
231    }
232
233    private static void unsetDotted(Graphics g) {
234        ((Graphics2D) g).setStroke(new BasicStroke());
235    }
236}