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

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