Changeset 21801 in osm for applications/editors/josm/plugins/buildings_tools/src
- Timestamp:
- 2010-06-19T19:31:12+02:00 (15 years ago)
- Location:
- applications/editors/josm/plugins/buildings_tools/src/buildings_tools
- Files:
-
- 2 added
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/buildings_tools/src/buildings_tools/Building.java
r21702 r21801 29 29 private EastNorth en3; 30 30 private EastNorth en4; 31 31 32 32 private EastNorth p1; 33 33 private Node node; 34 34 double meter = 0; 35 35 36 36 private double len = 0; 37 private double lwidth;37 private double width; 38 38 private double heading; 39 private boolean ang constrainted;40 private double ang constraint = 0;41 39 private boolean angConstrained; 40 private double angConstraint = 0; 41 42 42 public void disableAngConstraint() { 43 angconstrainted = false; 44 } 43 angConstrained = false; 44 } 45 45 46 public void setAngConstraint(double angle) { 46 ang constrainted = true;47 ang constraint = angle;48 while (angconstraint>(Math.PI/4)) angconstraint-=Math.PI/4;49 } 47 angConstrained = true; 48 angConstraint = angle; 49 } 50 50 51 public double getLength() { 51 52 return len; 52 53 } 53 54 55 public double getWidth() { 56 return width; 57 } 58 59 public boolean isRectDrawing() { 60 return angConstrained && ToolSettings.getWidth() == 0 && ToolSettings.getLenStep() == 0; 61 } 62 54 63 public void reset() { 55 64 len = 0; 56 en1=null; 57 en2=null; 58 en3=null; 59 en4=null; 60 } 61 public EastNorth Point1() { return en1; } 62 public EastNorth Point2() { return en2; } 63 public EastNorth Point3() { return en3; } 64 public EastNorth Point4() { return en4; } 65 en1 = null; 66 en2 = null; 67 en3 = null; 68 en4 = null; 69 } 70 71 public EastNorth point1() { 72 return en1; 73 } 74 75 public EastNorth point2() { 76 return en2; 77 } 78 79 public EastNorth point3() { 80 return en3; 81 } 82 83 public EastNorth point4() { 84 return en4; 85 } 86 65 87 private void updMetrics() { 66 meter = 2 *Math.PI/(Math.cos(Math.toRadians(eastNorth2latlon(p1).lat())) * eqlen);88 meter = 2 * Math.PI / (Math.cos(Math.toRadians(eastNorth2latlon(p1).lat())) * eqlen); 67 89 reset(); 68 90 } 91 69 92 public void setBase(EastNorth base) { 70 93 node = null; … … 72 95 updMetrics(); 73 96 } 97 74 98 public void setBase(Node base) { 75 99 node = base; … … 77 101 updMetrics(); 78 102 } 103 104 /** 105 * @returns Projection of the point to the heading vector in metres 106 */ 107 private double projection1(EastNorth p) { 108 final EastNorth vec = p1.sub(p); 109 return (Math.sin(heading) * vec.east() + Math.cos(heading) * vec.north()) / meter; 110 } 111 112 /** 113 * @returns Projection of the point to the perpendicular of the heading 114 * vector in metres 115 */ 116 private double projection2(EastNorth p) { 117 final EastNorth vec = p1.sub(p); 118 return (Math.cos(heading) * vec.east() - Math.sin(heading) * vec.north()) / meter; 119 } 120 79 121 private void updatePos() { 122 if (len == 0) 123 return; 80 124 en1 = p1; 81 en2 = new EastNorth(p1.east()+Math.sin(heading)*len*meter,p1.north()+Math.cos(heading)*len*meter); 82 en3 = new EastNorth(p1.east()+Math.sin(heading)*len*meter+Math.cos(heading)*lwidth*meter,p1.north()+Math.cos(heading)*len*meter-Math.sin(heading)*lwidth*meter); 83 en4 = new EastNorth(p1.east()+Math.cos(heading)*lwidth*meter,p1.north()-Math.sin(heading)*lwidth*meter); 84 } 85 public void setPlace(EastNorth p2,double width,double lenstep,boolean ignoreConstraint) { 86 heading = p1.heading(p2); 125 en2 = new EastNorth(p1.east() + Math.sin(heading) * len * meter, p1.north() + Math.cos(heading) * len * meter); 126 en3 = new EastNorth(p1.east() + Math.sin(heading) * len * meter + Math.cos(heading) * width * meter, p1.north() 127 + Math.cos(heading) * len * meter - Math.sin(heading) * width * meter); 128 en4 = new EastNorth(p1.east() + Math.cos(heading) * width * meter, p1.north() - Math.sin(heading) * width 129 * meter); 130 } 131 132 public void setLengthWidth(double length, double width) { 133 this.len = length; 134 this.width = width; 135 updatePos(); 136 } 137 138 public void setWidth(EastNorth p3) { 139 this.width = projection2(p3); 140 updatePos(); 141 } 142 143 public void setPlace(EastNorth p2, double width, double lenstep, boolean ignoreConstraints) { 144 this.heading = p1.heading(p2); 87 145 double hdang = 0; 88 if (angconstrainted && !ignoreConstraint) { 89 hdang = Math.round((heading-angconstraint)/Math.PI*4); 90 if (hdang>=8)hdang-=8; 91 if (hdang<0)hdang+=8; 92 heading = hdang*Math.PI/4+angconstraint; 93 } 94 double distance = eastNorth2latlon(p1).greatCircleDistance(eastNorth2latlon(p2)); 95 if (lenstep <= 0) len=distance; else len = Math.round(distance/lenstep)*lenstep; 96 if (len == 0) return; 97 lwidth = width; 146 if (angConstrained && !ignoreConstraints) { 147 hdang = Math.round((heading - angConstraint) / Math.PI * 4); 148 hdang = hdang % 8; 149 if (hdang < 0) 150 hdang += 8; 151 heading = (hdang * Math.PI / 4 + angConstraint) % (2 * Math.PI); 152 } 153 154 this.width = width; 155 this.len = projection1(p2); 156 if (lenstep > 0 && !ignoreConstraints) 157 this.len = Math.round(this.len / lenstep) * lenstep; 158 98 159 updatePos(); 160 99 161 Main.map.statusLine.setHeading(Math.toDegrees(heading)); 100 if (angconstrainted && !ignoreConstraint) { 101 Main.map.statusLine.setAngle(hdang*45); 102 } 103 } 104 public void setWidth(double width) { 105 lwidth = width; 106 updatePos(); 107 } 162 if (angConstrained && !ignoreConstraints) { 163 Main.map.statusLine.setAngle(hdang * 45); 164 } 165 } 166 167 public void setPlaceRect(EastNorth p2) { 168 if (!isRectDrawing()) 169 throw new IllegalStateException("Invalid drawing mode"); 170 heading = angConstraint; 171 setLengthWidth(projection1(p2), projection2(p2)); 172 Main.map.statusLine.setHeading(Math.toDegrees(heading)); 173 } 174 175 public void angFix(EastNorth point) { 176 EastNorth en3 = this.en3; 177 heading = p1.heading(point); 178 setLengthWidth(projection1(en3), projection2(en3)); 179 this.en3 = en3; 180 } 181 108 182 public void paint(Graphics2D g, MapView mv) { 109 if (len == 0) return; 183 if (len == 0) 184 return; 110 185 GeneralPath b = new GeneralPath(); 111 186 Point pp1 = mv.getPoint(eastNorth2latlon(en1)); … … 114 189 Point pp4 = mv.getPoint(eastNorth2latlon(en3)); 115 190 116 b.moveTo(pp1.x, pp1.y); b.lineTo(pp3.x, pp3.y); 117 b.lineTo(pp4.x, pp4.y); b.lineTo(pp2.x, pp2.y); 191 b.moveTo(pp1.x, pp1.y); 192 b.lineTo(pp3.x, pp3.y); 193 b.lineTo(pp4.x, pp4.y); 194 b.lineTo(pp2.x, pp2.y); 118 195 b.lineTo(pp1.x, pp1.y); 119 196 g.draw(b); 120 197 } 198 121 199 public Way create() { 122 if (len == 0) return null; 200 if (len == 0) 201 return null; 123 202 Node n1; 124 if (node ==null)203 if (node == null) 125 204 n1 = new Node(eastNorth2latlon(en1)); 126 205 else … … 129 208 Node n3 = new Node(eastNorth2latlon(en3)); 130 209 Node n4 = new Node(eastNorth2latlon(en4)); 131 if (n1.getCoor().isOutSideWorld() ||n2.getCoor().isOutSideWorld()||132 n3.getCoor().isOutSideWorld() ||n4.getCoor().isOutSideWorld()) {210 if (n1.getCoor().isOutSideWorld() || n2.getCoor().isOutSideWorld() || 211 n3.getCoor().isOutSideWorld() || n4.getCoor().isOutSideWorld()) { 133 212 JOptionPane.showMessageDialog(Main.parent, 134 tr("Cannot place building outside of the world."));213 tr("Cannot place building outside of the world.")); 135 214 return null; 136 215 } 137 216 Way w = new Way(); 138 217 w.addNode(n1); 139 if ( lwidth>=0) {218 if (projection1(en3) > 0) { 140 219 w.addNode(n2); 141 220 w.addNode(n3); … … 147 226 } 148 227 w.addNode(n1); 149 w.put("building", "yes");228 w.put("building", ToolSettings.getTag()); 150 229 Collection<Command> cmds = new LinkedList<Command>(); 151 if (node==null) cmds.add(new AddCommand(n1)); 230 if (node == null) 231 cmds.add(new AddCommand(n1)); 152 232 cmds.add(new AddCommand(n2)); 153 233 cmds.add(new AddCommand(n3)); -
applications/editors/josm/plugins/buildings_tools/src/buildings_tools/BuildingSizeAction.java
r21236 r21801 11 11 @SuppressWarnings("serial") 12 12 public class BuildingSizeAction extends JosmAction { 13 13 14 14 public BuildingSizeAction() { 15 super(tr("Set buildings size"), "mapmode/building",tr("Set buildings size"),15 super(tr("Set buildings size"), "mapmode/building", tr("Set buildings size"), 16 16 Shortcut.registerShortcut("edit:buildingsdialog", 17 18 KeyEvent.VK_W, Shortcut.GROUP_EDIT,19 17 tr("Edit: {0}", tr("Set buildings size")), 18 KeyEvent.VK_W, Shortcut.GROUP_EDIT, 19 Shortcut.SHIFT_DEFAULT), 20 20 true); 21 21 } 22 22 23 public void actionPerformed(ActionEvent arg0) { 23 24 BuildingSizeDialog dlg = new BuildingSizeDialog(); 24 int answer = dlg.getValue(); 25 if (answer == 1) { 26 DrawBuildingAction.SetSizes(dlg.width(), dlg.lenstep()); 27 DrawBuildingAction.SetAddrDialog(dlg.useAddr()); 25 if (dlg.getValue() == 1) { 26 ToolSettings.setSizes(dlg.width(), dlg.lenstep()); 27 ToolSettings.setAddrDialog(dlg.useAddr()); 28 28 } 29 29 } 30 31 30 } -
applications/editors/josm/plugins/buildings_tools/src/buildings_tools/BuildingSizeDialog.java
r21236 r21801 5 5 import java.awt.Component; 6 6 import java.awt.GridBagLayout; 7 import java.awt.event.ActionEvent; 8 import java.awt.event.ActionListener; 7 9 import java.text.NumberFormat; 8 10 import java.text.ParseException; 9 11 12 import javax.swing.JButton; 10 13 import javax.swing.JFormattedTextField; 11 14 import javax.swing.JCheckBox; … … 21 24 private JFormattedTextField twidth = new JFormattedTextField(NumberFormat.getInstance()); 22 25 private JFormattedTextField tlenstep = new JFormattedTextField(NumberFormat.getInstance()); 23 private JPanel panel = new JPanel(new GridBagLayout());24 26 private JCheckBox caddr = new JCheckBox(tr("Use Address dialog")); 25 private void addLabelled(String str, Component c) { 27 28 static void addLabelled(JPanel panel, String str, Component c) { 26 29 JLabel label = new JLabel(str); 27 30 panel.add(label, GBC.std()); … … 29 32 panel.add(c, GBC.eol().fill(GBC.HORIZONTAL)); 30 33 } 34 31 35 public BuildingSizeDialog() { 32 super(Main.parent, tr("Set buildings size"), 36 super(Main.parent, tr("Set buildings size"), 33 37 new String[] { tr("OK"), tr("Cancel") }, 34 38 true); 35 contentConstraints = GBC.eol().fill().insets(15,15,15,5); 36 setButtonIcons(new String[] {"ok.png", "cancel.png" }); 37 38 addLabelled(tr("Buildings width:"),twidth); 39 addLabelled(tr("Length step:"),tlenstep); 40 twidth.setValue(DrawBuildingAction.getWidth()); 41 tlenstep.setValue(DrawBuildingAction.getLenStep()); 42 panel.add(caddr,GBC.eol().fill(GBC.HORIZONTAL)); 39 contentConstraints = GBC.eol().fill().insets(15, 15, 15, 5); 40 setButtonIcons(new String[] { "ok.png", "cancel.png" }); 41 42 final JPanel panel = new JPanel(new GridBagLayout()); 43 addLabelled(panel, tr("Buildings width:"), twidth); 44 addLabelled(panel, tr("Length step:"), tlenstep); 45 panel.add(caddr, GBC.eol().fill(GBC.HORIZONTAL)); 46 47 twidth.setValue(ToolSettings.getWidth()); 48 tlenstep.setValue(ToolSettings.getLenStep()); 49 caddr.setSelected(ToolSettings.isUsingAddr()); 50 51 JButton bAdv = new JButton(tr("Advanced...")); 52 bAdv.addActionListener(new ActionListener() { 53 @Override 54 public void actionPerformed(ActionEvent arg0) { 55 AdvancedSettingsDialog dlg = new AdvancedSettingsDialog(); 56 if (dlg.getValue() == 1) { 57 ToolSettings.setTag(dlg.getTag()); 58 ToolSettings.setBBMode(dlg.isBBMode()); 59 } 60 } 61 }); 62 panel.add(bAdv, GBC.eol().insets(0, 5, 0, 0).anchor(GBC.EAST)); 63 43 64 setContent(panel); 44 65 setupDialog(); 45 66 setVisible(true); 46 67 } 68 47 69 public double width() { 48 70 try { 49 71 return NumberFormat.getInstance().parse(twidth.getText()).doubleValue(); 50 } catch (ParseException e) { 72 } catch (ParseException e) { 51 73 return 0; 52 74 } 53 75 } 76 54 77 public double lenstep() { 55 78 try { 56 79 return NumberFormat.getInstance().parse(tlenstep.getText()).doubleValue(); 57 } catch (ParseException e) { 58 80 } catch (ParseException e) { 81 return 0; 59 82 } 60 83 } 84 61 85 public boolean useAddr() { 62 86 return caddr.isSelected(); -
applications/editors/josm/plugins/buildings_tools/src/buildings_tools/DrawBuildingAction.java
r21704 r21801 38 38 @SuppressWarnings("serial") 39 39 public class DrawBuildingAction extends MapMode 40 implements MapViewPaintable, AWTEventListener, SelectionChangedListener 41 { 42 enum Mode {None, Drawing, DrawingWidth} 40 implements MapViewPaintable, AWTEventListener, SelectionChangedListener { 41 enum Mode {None, Drawing, DrawingWidth, DrawingAngFix} 43 42 final private Cursor cursorCrosshair; 44 43 final private Cursor cursorJoinNode; 45 44 private Cursor currCursor; 46 45 47 private static double width = 0;48 private static double lenstep = 0;49 private static boolean useAddr;50 51 46 private Mode mode = Mode.None; 52 private EastNorth p1,p2,p3; 47 private Mode nextMode = Mode.None; 48 53 49 private Color selectedColor; 54 50 private Point mousePos; 55 56 51 private Point drawStartPos; 57 52 58 53 Building building = new Building(); 59 54 60 public static void SetAddrDialog(boolean _useAddr) {61 useAddr = _useAddr;62 }63 public static void SetSizes(double newwidth,double newlenstep) {64 width = newwidth;65 lenstep = newlenstep;66 }67 public static double getWidth() {68 return width;69 }70 71 public static double getLenStep() {72 return lenstep;73 }74 55 public DrawBuildingAction(MapFrame mapFrame) { 75 56 super(tr("Draw buildings"),"building",tr("Draw buildings"), … … 136 117 Main.map.mapView.addTemporaryLayer(this); 137 118 DataSet.selListeners.add(this); 138 UpdateConstraint(getCurrentDataSet().getSelected());119 updateConstraint(getCurrentDataSet().getSelected()); 139 120 try { 140 121 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK); … … 159 140 if(Main.map == null || Main.map.mapView == null) 160 141 return; 142 Main.map.statusLine.setHeading(-1); 143 Main.map.statusLine.setAngle(-1); 144 building.reset(); 161 145 Main.map.mapView.repaint(); 146 updateStatusLine(); 162 147 } 163 148 … … 168 153 cancelDrawing(); 169 154 } 170 171 private void ProcessMouseEvent(MouseEvent e) { 155 156 private EastNorth getPoint(MouseEvent e) { 157 Node n; 158 if (e.isControlDown()) { 159 n = null; 160 } else { 161 n = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isUsablePredicate); 162 } 163 if (n == null) { 164 return latlon2eastNorth(Main.map.mapView.getLatLon(mousePos.x, mousePos.y)); 165 } else { 166 return latlon2eastNorth(n.getCoor()); 167 } 168 } 169 170 private Mode modeDrawing(MouseEvent e) { 171 EastNorth p = getPoint(e); 172 if (building.isRectDrawing() && (!e.isShiftDown() || ToolSettings.isBBMode())) { 173 building.setPlaceRect(p); 174 return e.isShiftDown() ? Mode.DrawingAngFix : Mode.None; 175 } else { 176 building.setPlace(p, ToolSettings.getWidth(), 177 ToolSettings.getLenStep(),e.isShiftDown()); 178 Main.map.statusLine.setDist(building.getLength()); 179 return this.nextMode = ToolSettings.getWidth() == 0? Mode.DrawingWidth : Mode.None; 180 } 181 } 182 183 private Mode modeDrawingWidth(MouseEvent e) { 184 building.setWidth(getPoint(e)); 185 Main.map.statusLine.setDist(Math.abs(building.getWidth())); 186 return Mode.None; 187 } 188 189 private Mode modeDrawingAngFix(MouseEvent e) { 190 building.angFix(getPoint(e)); 191 return Mode.None; 192 } 193 194 private void processMouseEvent(MouseEvent e) { 172 195 mousePos = e.getPoint(); 173 if (mode == Mode.None) return; 174 Node n; 196 if (mode == Mode.None) { 197 nextMode = Mode.None; 198 return; 199 } 200 175 201 if (mode == Mode.Drawing) { 176 if (e.isControlDown()) { 177 n = null; 178 } else { 179 n = Main.map.mapView.getNearestNode(mousePos); 180 } 181 if (n == null) { 182 p2 = latlon2eastNorth(Main.map.mapView.getLatLon(mousePos.x, mousePos.y)); 183 } else { 184 p2 = latlon2eastNorth(n.getCoor()); 185 } 186 building.setPlace(p2, width, e.isShiftDown()?0:lenstep,e.isShiftDown()); 187 Main.map.statusLine.setDist(building.getLength()); 188 return; 189 } 190 if (mode == Mode.DrawingWidth) { 191 if (e.isControlDown()) { 192 n = null; 193 } else { 194 n = Main.map.mapView.getNearestNode(mousePos); 195 } 196 if (n == null) { 197 p3 = latlon2eastNorth(Main.map.mapView.getLatLon(mousePos.x, mousePos.y)); 198 } else { 199 p3 = latlon2eastNorth(n.getCoor()); 200 } 201 double mwidth = 202 ((p3.east()-p2.east())*(p2.north()-p1.north())+ 203 (p3.north()-p2.north())*(p1.east()-p2.east())) 204 /p1.distanceSq(p2) * building.getLength(); 205 206 building.setWidth(mwidth); 207 Main.map.statusLine.setDist(Math.abs(mwidth)); 208 return; 209 } 210 } 202 nextMode = modeDrawing(e); 203 } else if (mode == Mode.DrawingWidth) { 204 nextMode = modeDrawingWidth(e); 205 } else if (mode == Mode.DrawingAngFix) { 206 nextMode = modeDrawingAngFix(e); 207 } else 208 throw new AssertionError("Invalid drawing mode"); 209 } 210 211 211 public void paint(Graphics2D g, MapView mv,Bounds bbox) 212 212 { … … 228 228 drawStartPos = mousePos; 229 229 230 Node n = Main.map.mapView.getNearestNode(mousePos );230 Node n = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isUsablePredicate); 231 231 if (n == null) { 232 p1 = latlon2eastNorth(Main.map.mapView.getLatLon(mousePos.x, mousePos.y)); 233 building.setBase(p1); 232 building.setBase(latlon2eastNorth(Main.map.mapView.getLatLon(mousePos.x, mousePos.y))); 234 233 } else { 235 p1 = latlon2eastNorth(n.getCoor());236 234 building.setBase(n); 237 235 } … … 241 239 242 240 private void drawingAdvance(MouseEvent e) { 243 ProcessMouseEvent(e); 244 if (building.getLength() > 0) { 245 if (width == 0 && mode == Mode.Drawing) { 246 p2 = building.Point2(); 247 mode = Mode.DrawingWidth; 248 updateStatusLine(); 249 return; 250 } 241 processMouseEvent(e); 242 if (this.mode != Mode.None && this.nextMode == Mode.None) { 243 drawingFinish(); 244 } else { 245 mode = this.nextMode; 246 updateStatusLine(); 247 } 248 } 249 250 private void drawingFinish() { 251 if (building.getLength() != 0) { 251 252 Way w = building.create(); 252 if (w != null && useAddr)253 if (w != null && ToolSettings.isUsingAddr()) 253 254 showAddrDialog(w); 254 255 } 255 Main.map.mapView.repaint(); 256 mode = Mode.None; 257 Main.map.statusLine.setHeading(-1); 258 Main.map.statusLine.setAngle(-1); 259 building.reset(); 260 updateStatusLine(); 256 cancelDrawing(); 261 257 } 262 258 … … 270 266 271 267 @Override public void mouseDragged(MouseEvent e) { 272 ProcessMouseEvent(e);268 processMouseEvent(e); 273 269 updCursor(); 274 270 if (mode!=Mode.None) Main.map.mapView.repaint(); … … 283 279 drawStartPos = null; 284 280 285 if ((mode == Mode.Drawing && dragged) || mode == Mode.DrawingWidth) 286 drawingAdvance(e); 281 if (mode == Mode.Drawing && !dragged) return; 282 if (mode == Mode.None) return; 283 284 drawingAdvance(e); 287 285 } 288 286 289 287 private void updCursor() { 290 288 if (mousePos==null) return; 291 Node n = Main.map.mapView.getNearestNode(mousePos );289 Node n = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isUsablePredicate); 292 290 if (n != null) setCursor(cursorJoinNode); else setCursor(cursorCrosshair); 293 291 … … 295 293 @Override public void mouseMoved(MouseEvent e) { 296 294 if(!Main.map.mapView.isActiveLayerDrawable()) return; 297 ProcessMouseEvent(e);295 processMouseEvent(e); 298 296 updCursor(); 299 297 if (mode!=Mode.None) Main.map.mapView.repaint(); … … 301 299 302 300 @Override public String getModeHelpText() { 303 if (mode==Mode.None) return tr("Point on the corner of building to start drawing");304 if (mode==Mode.Drawing) return tr("Point on opposite end of building");305 if (mode==Mode.DrawingWidth) return tr("Set width of building");301 if (mode==Mode.None) return tr("Point on the corner of the building to start drawing"); 302 if (mode==Mode.Drawing) return tr("Point on opposite end of the building"); 303 if (mode==Mode.DrawingWidth) return tr("Set width of the building"); 306 304 return ""; 307 305 } … … 311 309 } 312 310 313 public void UpdateConstraint(Collection<? extends OsmPrimitive> newSelection) {311 public void updateConstraint(Collection<? extends OsmPrimitive> newSelection) { 314 312 building.disableAngConstraint(); 315 313 if (newSelection.size()!=2)return; … … 323 321 324 322 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 325 UpdateConstraint(newSelection);323 updateConstraint(newSelection); 326 324 } 327 325 }
Note:
See TracChangeset
for help on using the changeset viewer.