Changeset 12156 in josm for trunk/src/org/openstreetmap
- Timestamp:
- 2017-05-15T14:14:40+02:00 (8 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java
r11747 r12156 3 3 4 4 import java.io.File; 5 import java.text.MessageFormat; 6 import java.util.AbstractCollection; 7 import java.util.ArrayList; 5 8 import java.util.Collection; 6 9 import java.util.Collections; … … 9 12 import java.util.HashSet; 10 13 import java.util.Iterator; 11 import java.util.LinkedList;12 14 import java.util.Map; 13 15 import java.util.NoSuchElementException; 14 16 import java.util.Set; 17 import java.util.stream.Stream; 15 18 16 19 import org.openstreetmap.josm.Main; … … 19 22 import org.openstreetmap.josm.data.DataSource; 20 23 import org.openstreetmap.josm.data.coor.EastNorth; 24 import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener; 25 import org.openstreetmap.josm.tools.ListenerList; 21 26 22 27 /** … … 35 40 public String creator; 36 41 37 /** Tracks */ 38 public final Collection<GpxTrack> tracks = new LinkedList<>(); 39 /** Routes */ 40 public final Collection<GpxRoute> routes = new LinkedList<>(); 41 /** Waypoints */ 42 public final Collection<WayPoint> waypoints = new LinkedList<>(); 42 private ArrayList<GpxTrack> privateTracks = new ArrayList<>(); 43 private ArrayList<GpxRoute> privateRoutes = new ArrayList<>(); 44 private ArrayList<WayPoint> privateWaypoints = new ArrayList<>(); 45 private final GpxTrackChangeListener proxy = e -> fireInvalidate(); 46 47 /** 48 * Tracks. Access is discouraged, use {@link #getTracks()} to read. 49 * @see #getTracks() 50 */ 51 public final Collection<GpxTrack> tracks = new ListeningCollection<GpxTrack>(privateTracks, this::fireInvalidate) { 52 53 @Override 54 protected void removed(GpxTrack cursor) { 55 cursor.removeListener(proxy); 56 super.removed(cursor); 57 } 58 59 @Override 60 protected void added(GpxTrack cursor) { 61 super.added(cursor); 62 cursor.addListener(proxy); 63 } 64 }; 65 66 /** 67 * Routes. Access is discouraged, use {@link #getTracks()} to read. 68 * @see #getRoutes() 69 */ 70 public final Collection<GpxRoute> routes = new ListeningCollection<>(privateRoutes, this::fireInvalidate); 71 72 /** 73 * Waypoints. Access is discouraged, use {@link #getTracks()} to read. 74 * @see #getWaypoints() 75 */ 76 public final Collection<WayPoint> waypoints = new ListeningCollection<>(privateWaypoints, this::fireInvalidate); 43 77 44 78 /** … … 49 83 */ 50 84 public final Set<DataSource> dataSources = new HashSet<>(); 85 86 private final ListenerList<GpxDataChangeListener> listeners = ListenerList.create(); 51 87 52 88 /** … … 72 108 } 73 109 } 74 tracks.addAll(other.tracks);75 routes.addAll(other.routes);76 waypoints.addAll(other.waypoints);110 privateTracks.addAll(other.getTracks()); 111 privateRoutes.addAll(other.getRoutes()); 112 privateWaypoints.addAll(other.getWaypoints()); 77 113 dataSources.addAll(other.dataSources); 114 fireInvalidate(); 115 } 116 117 /** 118 * Get all tracks contained in this data set. 119 * @return The tracks. 120 */ 121 public Collection<GpxTrack> getTracks() { 122 return Collections.unmodifiableCollection(privateTracks); 123 } 124 125 /** 126 * Add a new track 127 * @param track The new track 128 * @since 12156 129 */ 130 public void addTrack(GpxTrack track) { 131 if (privateTracks.contains(track)) { 132 throw new IllegalArgumentException(MessageFormat.format("The track was already added to this data: {0}", track)); 133 } 134 privateTracks.add(track); 135 track.addListener(proxy); 136 fireInvalidate(); 137 } 138 139 /** 140 * Remove a track 141 * @param track The old track 142 * @since 12156 143 */ 144 public void removeTrack(GpxTrack track) { 145 if (!privateTracks.remove(track)) { 146 throw new IllegalArgumentException(MessageFormat.format("The track was not in this data: {0}", track)); 147 } 148 track.removeListener(proxy); 149 fireInvalidate(); 150 } 151 152 /** 153 * Gets the list of all routes defined in this data set. 154 * @return The routes 155 * @since 12156 156 */ 157 public Collection<GpxRoute> getRoutes() { 158 return Collections.unmodifiableCollection(privateRoutes); 159 } 160 161 /** 162 * Add a new route 163 * @param route The new route 164 * @since 12156 165 */ 166 public void addRoute(GpxRoute route) { 167 if (privateRoutes.contains(route)) { 168 throw new IllegalArgumentException(MessageFormat.format("The route was already added to this data: {0}", route)); 169 } 170 privateRoutes.add(route); 171 fireInvalidate(); 172 } 173 174 /** 175 * Remove a route 176 * @param route The old route 177 * @since 12156 178 */ 179 public void removeRoute(GpxRoute route) { 180 if (!privateRoutes.remove(route)) { 181 throw new IllegalArgumentException(MessageFormat.format("The route was not in this data: {0}", route)); 182 } 183 fireInvalidate(); 184 } 185 186 /** 187 * Gets a list of all way points in this data set. 188 * @return The way points. 189 * @since 12156 190 */ 191 public Collection<WayPoint> getWaypoints() { 192 return Collections.unmodifiableCollection(privateWaypoints); 193 } 194 195 /** 196 * Add a new waypoint 197 * @param waypoint The new waypoint 198 * @since 12156 199 */ 200 public void addWaypoint(WayPoint waypoint) { 201 if (privateWaypoints.contains(waypoint)) { 202 throw new IllegalArgumentException(MessageFormat.format("The route was already added to this data: {0}", waypoint)); 203 } 204 privateWaypoints.add(waypoint); 205 fireInvalidate(); 206 } 207 208 /** 209 * Remove a waypoint 210 * @param waypoint The old waypoint 211 * @since 12156 212 */ 213 public void removeWaypoint(WayPoint waypoint) { 214 if (!privateWaypoints.remove(waypoint)) { 215 throw new IllegalArgumentException(MessageFormat.format("The route was not in this data: {0}", waypoint)); 216 } 217 fireInvalidate(); 78 218 } 79 219 … … 83 223 */ 84 224 public boolean hasTrackPoints() { 85 for (GpxTrack trk : tracks) { 86 for (GpxTrackSegment trkseg : trk.getSegments()) { 87 if (!trkseg.getWayPoints().isEmpty()) 88 return true; 89 } 90 } 91 return false; 225 return getTrackPoints().findAny().isPresent(); 226 } 227 228 /** 229 * Gets a stream of all track points in the segments of the tracks of this data. 230 * @return The stream 231 * @see #getTracks() 232 * @see GpxTrack#getSegments() 233 * @see GpxTrackSegment#getWayPoints() 234 * @since 12156 235 */ 236 public Stream<WayPoint> getTrackPoints() { 237 return getTracks().stream().flatMap(trk -> trk.getSegments().stream()).flatMap(trkseg -> trkseg.getWayPoints().stream()); 92 238 } 93 239 … … 97 243 */ 98 244 public boolean hasRoutePoints() { 99 for (GpxRoute rte : routes) { 100 if (!rte.routePoints.isEmpty()) 101 return true; 102 } 103 return false; 245 return getRoutes().stream().anyMatch(rte -> !rte.routePoints.isEmpty()); 104 246 } 105 247 … … 144 286 public Bounds recalculateBounds() { 145 287 Bounds bounds = null; 146 for (WayPoint wpt : waypoints) {288 for (WayPoint wpt : getWaypoints()) { 147 289 if (bounds == null) { 148 290 bounds = new Bounds(wpt.getCoor()); … … 151 293 } 152 294 } 153 for (GpxRoute rte : routes) {295 for (GpxRoute rte : getRoutes()) { 154 296 for (WayPoint wpt : rte.routePoints) { 155 297 if (bounds == null) { … … 160 302 } 161 303 } 162 for (GpxTrack trk : tracks) {304 for (GpxTrack trk : getTracks()) { 163 305 Bounds trkBounds = trk.getBounds(); 164 306 if (trkBounds != null) { … … 178 320 */ 179 321 public double length() { 180 double result = 0.0; // in meters 181 182 for (GpxTrack trk : tracks) { 183 result += trk.length(); 184 } 185 186 return result; 322 return getTracks().stream().mapToDouble(GpxTrack::length).sum(); 187 323 } 188 324 … … 261 397 double py = p.north(); 262 398 double rx = 0.0, ry = 0.0, sx, sy, x, y; 263 if (tracks == null) 264 return null; 265 for (GpxTrack track : tracks) { 399 for (GpxTrack track : getTracks()) { 266 400 for (GpxTrackSegment seg : track.getSegments()) { 267 401 WayPoint r = null; … … 352 486 */ 353 487 public void resetEastNorthCache() { 354 if (waypoints != null) { 355 for (WayPoint wp : waypoints) { 488 getWaypoints().forEach(WayPoint::invalidateEastNorthCache); 489 getTrackPoints().forEach(WayPoint::invalidateEastNorthCache); 490 for (GpxRoute route: getRoutes()) { 491 if (route.routePoints == null) { 492 continue; 493 } 494 for (WayPoint wp: route.routePoints) { 356 495 wp.invalidateEastNorthCache(); 357 }358 }359 if (tracks != null) {360 for (GpxTrack track: tracks) {361 for (GpxTrackSegment segment: track.getSegments()) {362 for (WayPoint wp: segment.getWayPoints()) {363 wp.invalidateEastNorthCache();364 }365 }366 }367 }368 if (routes != null) {369 for (GpxRoute route: routes) {370 if (route.routePoints == null) {371 continue;372 }373 for (WayPoint wp: route.routePoints) {374 wp.invalidateEastNorthCache();375 }376 496 } 377 497 } … … 498 618 return true; 499 619 } 620 621 /** 622 * Adds a listener that gets called whenever the data changed. 623 * @param listener The listener 624 * @since 12156 625 */ 626 public void addChangeListener(GpxDataChangeListener listener) { 627 listeners.addListener(listener); 628 } 629 630 /** 631 * Adds a listener that gets called whenever the data changed. It is added with a weak link 632 * @param listener The listener 633 */ 634 public void addWeakChangeListener(GpxDataChangeListener listener) { 635 listeners.addWeakListener(listener); 636 } 637 638 /** 639 * Removes a listener that gets called whenever the data changed. 640 * @param listener The listener 641 * @since 12156 642 */ 643 public void removeChangeListener(GpxDataChangeListener listener) { 644 listeners.removeListener(listener); 645 } 646 647 private void fireInvalidate() { 648 GpxDataChangeEvent e = new GpxDataChangeEvent(this); 649 listeners.fireEvent(l -> l.gpxDataChanged(e)); 650 } 651 652 /** 653 * This is a proxy of a collection that notifies a listener on every collection change 654 * @author Michael Zangl 655 * 656 * @param <T> The entry type 657 * @since 12156 658 */ 659 private static class ListeningCollection<T> extends AbstractCollection<T> { 660 private final ArrayList<T> base; 661 private final Runnable runOnModification; 662 663 ListeningCollection(ArrayList<T> base, Runnable runOnModification) { 664 this.base = base; 665 this.runOnModification = runOnModification; 666 } 667 668 @Override 669 public Iterator<T> iterator() { 670 Iterator<T> it = base.iterator(); 671 return new Iterator<T>() { 672 private T cursor; 673 674 @Override 675 public boolean hasNext() { 676 return it.hasNext(); 677 } 678 679 @Override 680 public T next() { 681 cursor = it.next(); 682 return cursor; 683 } 684 685 @Override 686 public void remove() { 687 if (cursor != null) { 688 removed(cursor); 689 cursor = null; 690 } 691 it.remove(); 692 } 693 }; 694 } 695 696 @Override 697 public int size() { 698 return base.size(); 699 } 700 701 @Override 702 public boolean remove(Object o) { 703 boolean remove = base.remove(o); 704 if (remove) { 705 removed((T) o); 706 } 707 return remove; 708 } 709 710 @Override 711 public boolean add(T e) { 712 boolean add = base.add(e); 713 added(e); 714 return add; 715 } 716 717 protected void removed(T cursor) { 718 runOnModification.run(); 719 } 720 721 protected void added(T cursor) { 722 runOnModification.run(); 723 } 724 } 725 726 /** 727 * A listener that listens to GPX data changes. 728 * @author Michael Zangl 729 * @since 12156 730 */ 731 @FunctionalInterface 732 public interface GpxDataChangeListener { 733 /** 734 * Called when the gpx data changed. 735 * @param e The event 736 */ 737 void gpxDataChanged(GpxDataChangeEvent e); 738 } 739 740 /** 741 * A data change event in any of the gpx data. 742 * @author Michael Zangl 743 * @since 12156 744 */ 745 public static class GpxDataChangeEvent { 746 private final GpxData source; 747 748 GpxDataChangeEvent(GpxData source) { 749 super(); 750 this.source = source; 751 } 752 753 /** 754 * Get the data that was changed. 755 * @return The data. 756 */ 757 public GpxData getSource() { 758 return source; 759 } 760 } 500 761 } -
trunk/src/org/openstreetmap/josm/data/gpx/GpxTrack.java
r9949 r12156 40 40 * Returns the number of times this track has been changed. 41 41 * @return Number of times this track has been changed. Always 0 for read-only tracks 42 * @deprecated since 12156 Replaced by change listeners. 42 43 */ 43 int getUpdateCount(); 44 @Deprecated 45 default int getUpdateCount() { 46 // to allow removal 47 return 0; 48 } 49 50 /** 51 * Add a listener that listens to changes in the GPX track. 52 * @param l The listener 53 */ 54 default void addListener(GpxTrackChangeListener l) { 55 // nop 56 } 57 58 /** 59 * Remove a listener that listens to changes in the GPX track. 60 * @param l The listener 61 */ 62 default void removeListener(GpxTrackChangeListener l) { 63 // nop 64 } 65 66 /** 67 * A listener that listens to GPX track changes. 68 * @author Michael Zangl 69 * @since 12156 70 */ 71 @FunctionalInterface 72 public interface GpxTrackChangeListener { 73 /** 74 * Called when the gpx data changed. 75 * @param e The event 76 */ 77 void gpxDataChanged(GpxTrackChangeEvent e); 78 } 79 80 /** 81 * A track change event for the current track. 82 * @author Michael Zangl 83 * @since 12156 84 */ 85 class GpxTrackChangeEvent { 86 private final GpxTrack source; 87 88 GpxTrackChangeEvent(GpxTrack source) { 89 super(); 90 this.source = source; 91 } 92 93 /** 94 * Get the track that was changed. 95 * @return The track. 96 */ 97 public GpxTrack getSource() { 98 return source; 99 } 100 } 44 101 } -
trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
r12155 r12156 9 9 import java.io.File; 10 10 import java.text.DateFormat; 11 import java.util.ArrayList;12 11 import java.util.Arrays; 13 12 import java.util.Collection; … … 60 59 /** 61 60 * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide 61 * 62 * Call {@link #invalidate()} after each change! 63 * 64 * TODO: Make it private, make it respond to track changes. 62 65 */ 63 66 public boolean[] trackVisibility = new boolean[0]; 64 65 private final List<GpxTrack> lastTracks = new ArrayList<>(); // List of tracks at last paint66 private int lastUpdateCount;67 67 68 68 private final GpxDrawHelper drawHelper; … … 94 94 super(d.getString(GpxConstants.META_NAME)); 95 95 data = d; 96 data.addWeakChangeListener(e -> this.invalidate()); 96 97 drawHelper = new GpxDrawHelper(data); 97 98 SystemOfMeasurement.addSoMChangeListener(drawHelper); … … 151 152 } 152 153 153 if (!data. tracks.isEmpty()) {154 if (!data.getTracks().isEmpty()) { 154 155 info.append("<table><thead align='center'><tr><td colspan='5'>") 155 156 .append(trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size())) … … 159 160 .append("</td></tr></thead>"); 160 161 161 for (GpxTrack trk : data. tracks) {162 for (GpxTrack trk : data.getTracks()) { 162 163 info.append("<tr><td>"); 163 164 if (trk.getAttributes().containsKey(GpxConstants.GPX_NAME)) { … … 182 183 183 184 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>") 184 .append(trn("{0} route, ", "{0} routes, ", data. routes.size(), data.routes.size()))185 .append(trn("{0} waypoint", "{0} waypoints", data. waypoints.size(), data.waypoints.size())).append("<br></html>");185 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size())) 186 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br></html>"); 186 187 187 188 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString())); … … 240 241 } 241 242 242 info.append(trn("{0} track, ", "{0} tracks, ", data. tracks.size(), data.tracks.size()))243 .append(trn("{0} route, ", "{0} routes, ", data. routes.size(), data.routes.size()))244 .append(trn("{0} waypoint", "{0} waypoints", data. waypoints.size(), data.waypoints.size())).append("<br>")243 info.append(trn("{0} track, ", "{0} tracks, ", data.getTracks().size(), data.getTracks().size())) 244 .append(trn("{0} route, ", "{0} routes, ", data.getRoutes().size(), data.getRoutes().size())) 245 .append(trn("{0} waypoint", "{0} waypoints", data.getWaypoints().size(), data.getWaypoints().size())).append("<br>") 245 246 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))) 246 247 .append("<br></html>"); … … 251 252 public boolean isMergable(Layer other) { 252 253 return other instanceof GpxLayer; 253 }254 255 private int sumUpdateCount() {256 int updateCount = 0;257 for (GpxTrack track: data.tracks) {258 updateCount += track.getUpdateCount();259 }260 return updateCount;261 }262 263 @Override264 public boolean isChanged() {265 if (data.tracks.equals(lastTracks))266 return sumUpdateCount() != lastUpdateCount;267 else268 return true;269 254 } 270 255 … … 279 264 long from = fromDate.getTime(); 280 265 long to = toDate.getTime(); 281 for (GpxTrack trk : data. tracks) {266 for (GpxTrack trk : data.getTracks()) { 282 267 Date[] t = GpxData.getMinMaxTimeForTrack(trk); 283 268 … … 287 272 i++; 288 273 } 274 invalidate(); 289 275 } 290 276 … … 299 285 @Override 300 286 public void paint(Graphics2D g, MapView mv, Bounds box) { 301 lastUpdateCount = sumUpdateCount();302 lastTracks.clear();303 lastTracks.addAll(data.tracks);304 305 287 List<WayPoint> visibleSegments = listVisibleSegments(box); 306 288 if (!visibleSegments.isEmpty()) { … … 363 345 */ 364 346 private void ensureTrackVisibilityLength() { 365 final int l = data. tracks.size();347 final int l = data.getTracks().size(); 366 348 if (l == trackVisibility.length) 367 349 return; … … 371 353 trackVisibility[i] = true; 372 354 } 355 invalidate(); 373 356 } 374 357 -
trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
r12153 r12156 743 743 } 744 744 745 gpxData. tracks.add(new ImmutableGpxTrack(trk, trkAttr));745 gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr)); 746 746 }); 747 747 } -
trunk/src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java
r11608 r12156 218 218 layer.trackVisibility[table.convertRowIndexToModel(i)] = s.isSelectedIndex(i); 219 219 } 220 Main.map.mapView.preferenceChanged(null); 221 Main.map.repaint(100); 220 layer.invalidate(); 222 221 } 223 222 … … 292 291 layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i); 293 292 } 293 // layer has been changed 294 layer.invalidate(); 294 295 // ...sync with layer visibility instead to avoid having two ways to hide everything 295 296 layer.setVisible(v == 1 || !s.isSelectionEmpty()); -
trunk/src/org/openstreetmap/josm/gui/layer/gpx/ConvertToDataLayerAction.java
r11848 r12156 74 74 public DataSet convert() { 75 75 final DataSet ds = new DataSet(); 76 for (GpxTrack trk : layer.data. tracks) {76 for (GpxTrack trk : layer.data.getTracks()) { 77 77 for (GpxTrackSegment segment : trk.getSegments()) { 78 78 List<Node> nodes = new ArrayList<>(); -
trunk/src/org/openstreetmap/josm/gui/layer/gpx/MarkersFromNamedPointsAction.java
r10436 r12156 11 11 import org.openstreetmap.josm.Main; 12 12 import org.openstreetmap.josm.data.gpx.GpxData; 13 import org.openstreetmap.josm.data.gpx.GpxTrack;14 import org.openstreetmap.josm.data.gpx.GpxTrackSegment;15 import org.openstreetmap.josm.data.gpx.WayPoint;16 13 import org.openstreetmap.josm.gui.layer.GpxLayer; 17 14 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; … … 30 27 public void actionPerformed(ActionEvent e) { 31 28 GpxData namedTrackPoints = new GpxData(); 32 for (GpxTrack track : layer.data.tracks) { 33 for (GpxTrackSegment seg : track.getSegments()) { 34 for (WayPoint point : seg.getWayPoints()) { 35 if (point.attr.containsKey("name") || point.attr.containsKey("desc")) { 36 namedTrackPoints.waypoints.add(point); 37 } 38 } 39 } 40 } 29 layer.data.getTrackPoints() 30 .filter(point -> point.attr.containsKey("name") || point.attr.containsKey("desc")) 31 .forEach(namedTrackPoints.waypoints::add); 41 32 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", layer.getName()), layer.getAssociatedFile(), layer); 42 33 if (!ml.data.isEmpty()) { -
trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java
r11746 r12156 317 317 WayPoint w2 = null; 318 318 319 for (GpxTrack track : trackLayer.data. tracks) {319 for (GpxTrack track : trackLayer.data.getTracks()) { 320 320 for (GpxTrackSegment trackseg : track.getSegments()) { 321 321 for (WayPoint w: trackseg.getWayPoints()) { -
trunk/src/org/openstreetmap/josm/io/GpxReader.java
r11675 r12156 466 466 currentState = states.pop(); 467 467 convertUrlToLink(currentTrackAttr); 468 data. tracks.add(new ImmutableGpxTrack(currentTrack, currentTrackAttr));468 data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr)); 469 469 break; 470 470 case "name": … … 497 497 currentState = states.pop(); 498 498 convertUrlToLink(currentRoute.attr); 499 data. routes.add(currentRoute);499 data.addRoute(currentRoute); 500 500 break; 501 501 default: // Do nothing -
trunk/src/org/openstreetmap/josm/io/GpxWriter.java
r11374 r12156 186 186 187 187 private void writeWayPoints() { 188 for (WayPoint pnt : data. waypoints) {188 for (WayPoint pnt : data.getWaypoints()) { 189 189 wayPoint(pnt, WAY_POINT); 190 190 } … … 192 192 193 193 private void writeRoutes() { 194 for (GpxRoute rte : data. routes) {194 for (GpxRoute rte : data.getRoutes()) { 195 195 openln("rte"); 196 196 writeAttr(rte, RTE_TRK_KEYS); … … 203 203 204 204 private void writeTracks() { 205 for (GpxTrack trk : data. tracks) {205 for (GpxTrack trk : data.getTracks()) { 206 206 openln("trk"); 207 207 writeAttr(trk, RTE_TRK_KEYS);
Note:
See TracChangeset
for help on using the changeset viewer.