/
LayoutTrack.java
659 lines (601 loc) · 21.6 KB
/
LayoutTrack.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
package jmri.jmrit.display.layoutEditor;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JPopupMenu;
import jmri.JmriException;
import jmri.Turnout;
import jmri.util.ColorUtil;
import jmri.util.MathUtil;
/**
* Abstract base class for all layout track objects (PositionablePoint,
* TrackSegment, LayoutTurnout, LayoutSlip, LevelXing and LayoutTurntable)
*
* @author Dave Duchamp Copyright (C) 2009
* @author George Warner Copyright (c) 2017-2018
*/
public abstract class LayoutTrack {
// hit point types
public static final int NONE = 0;
public static final int POS_POINT = 1;
public static final int TURNOUT_A = 2; // throat for RH, LH, and WYE turnouts
public static final int TURNOUT_B = 3; // continuing route for RH and LH turnouts
public static final int TURNOUT_C = 4; // diverging route for RH and LH turnouts
public static final int TURNOUT_D = 5; // 4th route for crossovers;
public static final int LEVEL_XING_A = 6;
public static final int LEVEL_XING_B = 7;
public static final int LEVEL_XING_C = 8;
public static final int LEVEL_XING_D = 9;
public static final int TRACK = 10;
public static final int TURNOUT_CENTER = 11; // non-connection points should be last
public static final int LEVEL_XING_CENTER = 12;
public static final int TURNTABLE_CENTER = 13;
public static final int LAYOUT_POS_LABEL = 14;
public static final int LAYOUT_POS_JCOMP = 15;
public static final int MULTI_SENSOR = 16;
public static final int MARKER = 17;
public static final int TRACK_CIRCLE_CENTRE = 18;
public static final int SLIP_CENTER = 20; //should be @Deprecated (use SLIP_LEFT & SLIP_RIGHT instead)
public static final int SLIP_A = 21;
public static final int SLIP_B = 22;
public static final int SLIP_C = 23;
public static final int SLIP_D = 24;
public static final int SLIP_LEFT = 25;
public static final int SLIP_RIGHT = 26;
public static final int BEZIER_CONTROL_POINT_OFFSET_MIN = 30; // offset for TrackSegment Bezier control points (minimum)
public static final int BEZIER_CONTROL_POINT_OFFSET_MAX = 38; // offset for TrackSegment Bezier control points (maximum)
public static final int SHAPE_CENTER = 39;
public static final int SHAPE_POINT_OFFSET_MIN = 40; // offset for Shape points (minimum)
public static final int SHAPE_POINT_OFFSET_MAX = 49; // offset for Shape points (maximum)
public static final int TURNTABLE_RAY_OFFSET = 50; // offset for turntable connection points
// operational instance variables (not saved between sessions)
protected LayoutEditor layoutEditor = null;
protected String ident = "";
protected Point2D center = new Point2D.Double(50.0, 50.0);
// dashed line parameters (unused)
//protected static int minNumDashes = 3;
//protected static double maxDashLength = 10;
protected boolean hidden = false;
/**
* constructor method
*/
public LayoutTrack(@Nonnull String ident, @Nonnull Point2D c, @Nonnull LayoutEditor layoutEditor) {
this.ident = ident;
this.center = c;
this.layoutEditor = layoutEditor;
}
/**
* accessor methods
*/
public String getId() {
return ident;
}
public String getName() {
return ident;
}
/**
* get center coordinates
*
* @return the center coordinates
*/
public Point2D getCoordsCenter() {
return center;
}
/**
* set center coordinates
*
* @param p the coordinates to set
*/
public void setCoordsCenter(@Nonnull Point2D p) {
center = p;
}
/**
* @return true if this track segment has decorations
*/
public boolean hasDecorations() {
return false;
}
/**
* get decorations
*
* @return the decorations
*/
public Map<String, String> getDecorations() {
return decorations;
}
/**
* set decorations
*
* @param decorations to set
*/
public void setDecorations(Map<String, String> decorations) {
this.decorations = decorations;
}
protected Map<String, String> decorations = null;
protected Color getColorForTrackBlock(
@Nullable LayoutBlock layoutBlock, boolean forceBlockTrackColor) {
Color result = ColorUtil.CLEAR; // transparent
if (layoutBlock != null) {
if (forceBlockTrackColor) {
result = layoutBlock.getBlockTrackColor();
} else {
result = layoutBlock.getBlockColor();
}
}
return result;
}
// optional parameter forceTrack = false
protected Color getColorForTrackBlock(@Nullable LayoutBlock lb) {
return getColorForTrackBlock(lb, false);
}
protected Color setColorForTrackBlock(Graphics2D g2,
@Nullable LayoutBlock layoutBlock, boolean forceBlockTrackColor) {
Color result = getColorForTrackBlock(layoutBlock, forceBlockTrackColor);
g2.setColor(result);
return result;
}
// optional parameter forceTrack = false
protected Color setColorForTrackBlock(Graphics2D g2, @Nullable LayoutBlock lb) {
return setColorForTrackBlock(g2, lb, false);
}
public abstract boolean isMainline();
/**
* draw one line (Ballast, ties, center or 3rd rail, block lines)
*
* @param g2 the graphics context
* @param isMain true if drawing mainlines
* @param isBlock true if drawing block lines
*/
protected abstract void draw1(Graphics2D g2, boolean isMain, boolean isBlock);
/**
* draw two lines (rails)
*
* @param g2 the graphics context
* @param isMain true if drawing mainlines
* @param railDisplacement the offset from center to draw the lines
*/
protected abstract void draw2(Graphics2D g2, boolean isMain, float railDisplacement);
/**
* draw hidden track
*
* @param g2 the graphics context
*/
//protected abstract void drawHidden(Graphics2D g2);
//note: placeholder until I get this implemented in all sub-classes
//TODO: replace with abstract declaration (above)
protected void drawHidden(Graphics2D g2) {
//nothing to do here... move along...
}
/**
* highlight unconnected connections
*
* @param g2 the graphics context
* @param specificType the specific connection to draw (or NONE for all)
*/
protected abstract void highlightUnconnected(Graphics2D g2, int specificType);
// optional parameter specificType = NONE
protected void highlightUnconnected(Graphics2D g2) {
highlightUnconnected(g2, NONE);
}
/**
* draw the edit controls
*
* @param g2 the graphics context
*/
protected abstract void drawEditControls(Graphics2D g2);
/**
* draw the turnout controls
*
* @param g2 the graphics context
*/
protected abstract void drawTurnoutControls(Graphics2D g2);
/**
* draw track decorations
*
* @param g2 the graphics context
*/
//protected abstract void drawDecorations(Graphics2D g2);
//note: placeholder until I get this implemented in all sub-classes
//TODO: replace with abstract declaration (above)
protected void drawDecorations(Graphics2D g2) {
//nothing to do here... move along...
}
/**
* Get the hidden state of the track element.
*
* @return true if hidden; false otherwise
*/
public boolean isHidden() {
return hidden;
}
/**
* Get the hidden state of the track element.
*
* @return true if hidden; false otherwise
* @deprecated since 4.7.2; use {@link #isHidden()} instead
*/
@Deprecated // Java standard pattern for boolean getters is "isHidden()"
public boolean getHidden() {
return hidden;
}
public void setHidden(boolean hide) {
if (hidden != hide) {
hidden = hide;
if (layoutEditor != null) {
layoutEditor.redrawPanel();
}
}
}
/*
* non-accessor methods
*/
/**
* get turnout state string
*
* @param turnoutState of the turnout
* @return the turnout state string
*/
public String getTurnoutStateString(int turnoutState) {
String result = "";
if (turnoutState == Turnout.CLOSED) {
result = Bundle.getMessage("TurnoutStateClosed");
} else if (turnoutState == Turnout.THROWN) {
result = Bundle.getMessage("TurnoutStateThrown");
} else {
result = Bundle.getMessage("BeanStateUnknown");
}
return result;
}
/**
* Initialization method for LayoutTrack sub-classes. The following method
* is called for each instance after the entire LayoutEditor is loaded to
* set the specific objects for that instance
*
* @param le the layout editor
*/
public abstract void setObjects(@Nonnull LayoutEditor le);
/**
* scale this LayoutTrack's coordinates by the x and y factors
*
* @param xFactor the amount to scale X coordinates
* @param yFactor the amount to scale Y coordinates
*/
public abstract void scaleCoords(float xFactor, float yFactor);
/**
* translate this LayoutTrack's coordinates by the x and y factors
*
* @param xFactor the amount to translate X coordinates
* @param yFactor the amount to translate Y coordinates
*/
public abstract void translateCoords(float xFactor, float yFactor);
protected Point2D rotatePoint(@Nonnull Point2D p, double sineRot, double cosineRot) {
double cX = center.getX();
double cY = center.getY();
double deltaX = p.getX() - cX;
double deltaY = p.getY() - cY;
double x = cX + cosineRot * deltaX - sineRot * deltaY;
double y = cY + sineRot * deltaX + cosineRot * deltaY;
return new Point2D.Double(x, y);
}
/**
* find the hit (location) type for a point
*
* @param hitPoint the point
* @param useRectangles whether to use (larger) rectangles or
* (smaller) circles for hit testing
* @param requireUnconnected whether to only return hit types for free
* connections
* @return the location type for the point (or NONE)
* @since 7.4.3
*/
protected abstract int findHitPointType(@Nonnull Point2D hitPoint, boolean useRectangles, boolean requireUnconnected);
// optional useRectangles & requireUnconnected parameters default to false
protected int findHitPointType(@Nonnull Point2D p) {
return findHitPointType(p, false, false);
}
// optional requireUnconnected parameter defaults to false
protected int findHitPointType(@Nonnull Point2D p, boolean useRectangles) {
return findHitPointType(p, useRectangles, false);
}
/**
* @param hitType the hit point type
* @return true if this int is for a connection to a LayoutTrack
*/
protected static boolean isConnectionHitType(int hitType) {
boolean result = false; // assume failure (pessimist!)
switch (hitType) {
case POS_POINT:
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D:
case TRACK:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
result = true; // these are all connection types
break;
case NONE:
case TURNOUT_CENTER:
case LEVEL_XING_CENTER:
case TURNTABLE_CENTER:
case LAYOUT_POS_LABEL:
case LAYOUT_POS_JCOMP:
case MULTI_SENSOR:
case MARKER:
case TRACK_CIRCLE_CENTRE:
case SLIP_CENTER:
case SLIP_LEFT:
case SLIP_RIGHT:
default:
result = false; // these are not
break;
}
if (isBezierHitType(hitType)) {
result = false; // these are not
} else if (hitType >= TURNTABLE_RAY_OFFSET) {
result = true; // these are all connection types
}
return result;
} // isConnectionHitType
/**
* @param hitType the hit point type
* @return true if this hit type is for a layout control
*/
protected static boolean isControlHitType(int hitType) {
boolean result = false; // assume failure (pessimist!)
switch (hitType) {
case TURNOUT_CENTER:
case SLIP_CENTER:
case SLIP_LEFT:
case SLIP_RIGHT:
result = true; // these are all control types
break;
case POS_POINT:
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D:
case TRACK:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
case NONE:
case LEVEL_XING_CENTER:
case TURNTABLE_CENTER:
case LAYOUT_POS_LABEL:
case LAYOUT_POS_JCOMP:
case MULTI_SENSOR:
case MARKER:
case TRACK_CIRCLE_CENTRE:
default:
result = false; // these are not
break;
}
if (isBezierHitType(hitType)) {
result = false; // these are not control types
} else if (hitType >= TURNTABLE_RAY_OFFSET) {
result = true; // these are all control types
}
return result;
} // isControlHitType
protected static boolean isBezierHitType(int hitType) {
return (hitType >= BEZIER_CONTROL_POINT_OFFSET_MIN)
&& (hitType <= BEZIER_CONTROL_POINT_OFFSET_MAX);
}
/**
* @param hitType the hit point type
* @return true if this int is for a popup menu
*/
protected static boolean isPopupHitType(int hitType) {
boolean result = false; // assume failure (pessimist!)
switch (hitType) {
case LEVEL_XING_CENTER:
case POS_POINT:
case SLIP_CENTER:
case SLIP_LEFT:
case SLIP_RIGHT:
case TRACK:
case TRACK_CIRCLE_CENTRE:
case TURNOUT_CENTER:
case TURNTABLE_CENTER:
result = true; // these are all popup hit types
break;
case LAYOUT_POS_JCOMP:
case LAYOUT_POS_LABEL:
case LEVEL_XING_A:
case LEVEL_XING_B:
case LEVEL_XING_C:
case LEVEL_XING_D:
case MARKER:
case MULTI_SENSOR:
case NONE:
case SLIP_A:
case SLIP_B:
case SLIP_C:
case SLIP_D:
case TURNOUT_A:
case TURNOUT_B:
case TURNOUT_C:
case TURNOUT_D:
default:
result = false; // these are not
break;
}
if (isBezierHitType(hitType)) {
result = true; // these are all popup hit types
} else if (hitType >= TURNTABLE_RAY_OFFSET) {
result = true; // these are all popup hit types
}
return result;
} // isPopupHitType
/**
* return the coordinates for a specified connection type (abstract: should
* be overridden by ALL subclasses)
*
* @param connectionType the connection type
* @return the coordinates for the specified connection type
*/
public abstract Point2D getCoordsForConnectionType(int connectionType);
/**
* @return the bounds of this track
*/
public abstract Rectangle2D getBounds();
/**
* show the popup menu for this layout track
*
* @param mouseEvent the mouse down event that triggered this popup
* @return the popup menu for this layout track
*/
@Nonnull
protected abstract JPopupMenu showPopup(@Nullable MouseEvent mouseEvent);
/**
* show the popup menu for this layout track
*
* @param where to show the popup
* @return the popup menu for this layout track
*/
@Nonnull
protected JPopupMenu showPopup(Point2D where) {
return this.showPopup(new MouseEvent(
layoutEditor.getTargetPanel(), // source
MouseEvent.MOUSE_CLICKED, // id
System.currentTimeMillis(), // when
0, // modifiers
(int) where.getX(), (int) where.getY(), // where
0, // click count
true)); // popup trigger
}
/**
* show the popup menu for this layout track
*
* @return the popup menu for this layout track
*/
@Nonnull
protected JPopupMenu showPopup() {
Point2D where = MathUtil.multiply(getCoordsCenter(),
layoutEditor.getZoom());
return this.showPopup(where);
}
/**
* get the LayoutTrack connected at the specified connection type
*
* @param connectionType where on us to get the connection
* @return the LayoutTrack connected at the specified connection type
* @throws JmriException - if the connectionType is invalid
*/
public abstract LayoutTrack getConnection(int connectionType) throws JmriException;
/**
* set the LayoutTrack connected at the specified connection type
*
* @param connectionType where on us to set the connection
* @param o the LayoutTrack that is to be connected
* @param type where on the LayoutTrack we are connected
* @throws JmriException - if connectionType or type are invalid
*/
public abstract void setConnection(int connectionType, LayoutTrack o, int type) throws JmriException;
/**
* abstract method... subclasses should implement _IF_ they need to recheck
* their block boundaries
*/
protected abstract void reCheckBlockBoundary();
/**
* get the layout connectivity for this track
*
* @return the list of Layout Connectivity objects
*/
protected abstract List<LayoutConnectivity> getLayoutConnectivity();
/**
* return true if this connection type is disconnected
*
* @param connectionType the connection type to test
* @return true if the connection for this connection type is free
*/
public boolean isDisconnected(int connectionType) {
boolean result = false;
if (isConnectionHitType(connectionType)) {
try {
result = (null == getConnection(connectionType));
} catch (JmriException e) {
// this should never happen because isConnectionType() above would have caught an invalid connectionType.
}
}
return result;
}
/**
* return a list of the available connections for this layout track
*
* @return the list of available connections
* <p>
* note: used by LayoutEditorChecks.setupCheckUnConnectedTracksMenu()
* <p>
* (This could have just returned a boolean but I thought a list might be
* more useful (eventually... not currently being used; we just check to see
* if it's not empty.)
*/
@Nonnull
public abstract List<Integer> checkForFreeConnections();
/**
* determine if all the appropriate blocks have been assigned to this track
*
* @return true if all appropriate blocks have been assigned
* <p>
* note: used by LayoutEditorChecks.setupCheckUnBlockedTracksMenu()
*/
public abstract boolean checkForUnAssignedBlocks();
/**
* check this track and its neighbors for non-contiguous blocks
* <p>
* For each (non-null) blocks of this track do: #1) If it's got an entry in
* the blockNamesToTrackNameSetMap then #2) If this track is not in one of
* the TrackNameSets for this block #3) add a new set (with this
* block/track) to blockNamesToTrackNameSetMap and #4) check all the
* connections in this block (by calling the 2nd method below)
* <p>
* Basically, we're maintaining contiguous track sets for each block found
* (in blockNamesToTrackNameSetMap)
*
* @param blockNamesToTrackNameSetMaps hashmap of key:block names to lists
* of track name sets for those blocks
* <p>
* note: used by LayoutEditorChecks.setupCheckNonContiguousBlocksMenu()
*/
public abstract void checkForNonContiguousBlocks(
@Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetMaps);
/**
* recursive routine to check for all contiguous tracks in this blockName
*
* @param blockName the block that we're checking for
* @param TrackNameSet the set of track names in this block
*/
public abstract void collectContiguousTracksNamesInBlockNamed(
@Nonnull String blockName,
@Nonnull Set<String> TrackNameSet);
/**
* Assign all the layout blocks in this track
*
* @param layoutBlock to this layout block (used by the Tools menu's "Assign
* block to selection" item)
*/
public abstract void setAllLayoutBlocks(LayoutBlock layoutBlock);
//private final static Logger log
// = LoggerFactory.getLogger(LayoutTrack.class);
}