source: josm/src/org/openstreetmap/josm/actions/mapmode/CombineAction.java@ 16

Last change on this file since 16 was 16, checked in by imi, 19 years ago
  • reverted to 14, but kept the global Projection.
  • improved the preference settings for projections.
File size: 6.7 KB
Line 
1package org.openstreetmap.josm.actions.mapmode;
2
3import java.awt.Color;
4import java.awt.Graphics;
5import java.awt.Point;
6import java.awt.event.KeyEvent;
7import java.awt.event.MouseEvent;
8
9import javax.swing.JOptionPane;
10
11import org.openstreetmap.josm.data.osm.LineSegment;
12import org.openstreetmap.josm.data.osm.Node;
13import org.openstreetmap.josm.data.osm.OsmPrimitive;
14import org.openstreetmap.josm.data.osm.Track;
15import org.openstreetmap.josm.gui.Main;
16import org.openstreetmap.josm.gui.MapFrame;
17
18/**
19 * A MapMode that allows the user to combine two objects to a new one.
20 *
21 * When entering CombineAction, all selection is cleared.
22 *
23 * The user can select objects by dragging them to another object, so the object
24 * he pressed and the one he released the button are combined. No selection
25 * rectangle is supported.
26 *
27 * Even if the user don't press Alt, tracks instead of line segments are selected.
28 * This means, it is impossible to select non-pending line segments.
29 *
30 * Pressing Ctrl or Shift has no effect too.
31 *
32 * No object can be combined with an object it is already part of. E.g. line
33 * segment cannot be combined with a track it is part of. In case of such a
34 * constillation, the user is informed.
35 *
36 * When combining, the object the user pressed on is called <i>source</i> and
37 * the object the button was released on is called <i>target</i>.
38 *
39 * The following objects can be combined:
40 *
41 * - A line segment and a track can be combined if one of them is a pending line
42 * segment. This get integrated into the track.
43 * - Two tracks can be combined. The latter track get removed and all its
44 * segments are moved to the first track. This is only possible, if both
45 * tracks have no different value in any key.
46 * - Two areas can be combined, if they share at least one node, in which case
47 * the combined area span both areas. If the areas share more than one node,
48 * all lines between the areas get removed. This is only possible if both areas
49 * have no different value in any key.
50 *
51 * All other object combinations cannot be combined.
52 *
53 * TODO: This and AddLineSegmentAction are similar. Refactor both.
54 *
55 * @author imi
56 */
57public class CombineAction extends MapMode {
58
59 /**
60 * The object that was first selected as combine source.
61 */
62 private OsmPrimitive first;
63 /**
64 * The object that was last selected as combine target.
65 */
66 private OsmPrimitive second;
67 /**
68 * Whether a hint is drawn on screen or not.
69 */
70 private boolean combineHintDrawn = false;
71
72 /**
73 * Constructs a CombineAction. Mnemonic is "c".
74 */
75 public CombineAction(MapFrame mapFrame) {
76 super("Combine", "combine", "Combine objects together.", KeyEvent.VK_C, mapFrame);
77 }
78
79 @Override
80 public void registerListener() {
81 super.registerListener();
82 mv.addMouseListener(this);
83 mv.addMouseMotionListener(this);
84 ds.clearSelection();
85 }
86
87 @Override
88 public void unregisterListener() {
89 super.unregisterListener();
90 mv.removeMouseListener(this);
91 mv.removeMouseMotionListener(this);
92 drawCombineHint(false);
93 }
94
95 /**
96 * If nothing is selected, select the object nearest to the mouse. Else
97 * start the "display possible combining" phase and draw a hint what would
98 * be combined if user releases the button.
99 */
100 @Override
101 public void mousePressed(MouseEvent e) {
102 if (e.getButton() != MouseEvent.BUTTON1)
103 return;
104
105 OsmPrimitive clicked = mv.getNearest(e.getPoint(), true);
106 if (clicked == null || clicked instanceof Node)
107 return;
108
109 drawCombineHint(false);
110 first = second = clicked;
111 }
112
113 /**
114 * Updates the drawn combine hint if necessary.
115 */
116 @Override
117 public void mouseDragged(MouseEvent e) {
118 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
119 return;
120
121 OsmPrimitive clicked = mv.getNearest(e.getPoint(), true);
122 if (clicked == null || clicked == second || clicked instanceof Node)
123 return;
124
125 drawCombineHint(false);
126 second = clicked;
127 drawCombineHint(true);
128 }
129
130 /**
131 * Start combining (if there is something to combine).
132 */
133 @Override
134 public void mouseReleased(MouseEvent e) {
135 if (e.getButton() != MouseEvent.BUTTON1)
136 return;
137
138 if (first == null || second == null || first == second) {
139 first = null;
140 second = null;
141 return;
142 }
143
144 drawCombineHint(false);
145
146 if (first instanceof LineSegment && second instanceof LineSegment)
147 JOptionPane.showMessageDialog(Main.main, "Cannot combine two line segments. To create tracks use 'Add Track'.");
148 else if (first instanceof LineSegment && second instanceof Track)
149 combine((LineSegment)first, (Track)second);
150 else if (first instanceof Track && second instanceof LineSegment)
151 combine((LineSegment)second, (Track)first);
152 else if (first instanceof Track && second instanceof Track) {
153 if (!first.keyPropertiesMergable(second))
154 JOptionPane.showMessageDialog(Main.main, "Cannot combine because of different properties.");
155 else {
156 Track t1 = (Track)first;
157 Track t2 = (Track)second;
158 if (t1.getStartingNode() == t2.getEndingNode()) {
159 t1 = t2;
160 t2 = (Track)first;
161 }
162 t1.addAll(t2.segments());
163 if (t1.keys == null)
164 t1.keys = t2.keys;
165 else
166 t1.keys.putAll(t2.keys);
167 ds.removeTrack(t2);
168 }
169 }
170 mv.repaint();
171 }
172
173
174 /**
175 * Add the line segment to the track and remove it from the pending segments.
176 * @param ls The line segment to add
177 * @param t The track to add the line segment to
178 */
179 private void combine(LineSegment ls, Track t) {
180 if (!ds.pendingLineSegments().contains(ls))
181 throw new IllegalStateException("Should not be able to select non-pending line segments.");
182
183 ds.assignPendingLineSegment(ls, t, t.getStartingNode() != ls.getEnd());
184 }
185
186 /**
187 * Draws or removes the combine hint using the combineHint structure.
188 *
189 * @param draw <code>true</code> to draw the hint or
190 * <code>false</code> to remove it.
191 */
192 private void drawCombineHint(boolean draw) {
193 if (draw == combineHintDrawn)
194 return;
195 if (first == null || second == null)
196 return;
197 if (second == first)
198 return;
199
200 Graphics g = mv.getGraphics();
201 g.setColor(Color.BLACK);
202 g.setXORMode(Color.WHITE);
203 draw(g, first);
204 draw(g, second);
205 combineHintDrawn = !combineHintDrawn;
206 }
207
208 /**
209 * Draw a hint for the specified primitive
210 * @param g The graphic to draw into
211 * @param osm The primitive to draw a hint for.
212 */
213 private void draw(Graphics g, OsmPrimitive osm) {
214 if (osm instanceof LineSegment) {
215 LineSegment ls = (LineSegment)osm;
216 Point start = mv.getScreenPoint(ls.getStart().coor);
217 Point end = mv.getScreenPoint(ls.getEnd().coor);
218 if (mv.dataSet.pendingLineSegments().contains(osm) && g.getColor() == Color.GRAY)
219 g.drawLine(start.x, start.y, end.x, end.y);
220 else
221 g.drawLine(start.x, start.y, end.x, end.y);
222 } else if (osm instanceof Track) {
223 for (LineSegment ls : ((Track)osm).segments())
224 draw(g, ls);
225 }
226 }
227}
Note: See TracBrowser for help on using the repository browser.