tmf: Ellipse function names that don't fit in CallStackView
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / Utils.java
1 /*****************************************************************************
2 * Copyright (c) 2007, 2014 Intel Corporation, Ericsson
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Intel Corporation - Initial API and implementation
10 * Ruslan A. Scherbakov, Intel - Initial API and implementation
11 * Alvaro Sanchez-Leon - Udpated for TMF
12 * Patrick Tasse - Refactoring
13 * Marc-Andre Laperle - Add time zone preference
14 *****************************************************************************/
15
16 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
17
18 import java.text.NumberFormat;
19 import java.text.SimpleDateFormat;
20 import java.util.Date;
21 import java.util.Iterator;
22 import java.util.TimeZone;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.swt.graphics.Color;
27 import org.eclipse.swt.graphics.Device;
28 import org.eclipse.swt.graphics.GC;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.graphics.Rectangle;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.tracecompass.internal.tmf.ui.Messages;
33 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimePreferences;
34 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
35 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
36
37 /**
38 * General utilities and definitions used by the time graph widget
39 *
40 * @author Alvaro Sanchez-Leon
41 * @author Patrick Tasse
42 */
43 public class Utils {
44
45 private Utils() {
46 }
47
48 /** Time format for dates and timestamp */
49 public enum TimeFormat {
50 /** Relative to the start of the trace */
51 RELATIVE,
52
53 /**
54 * Absolute timestamp (ie, relative to the Unix epoch)
55 */
56 CALENDAR,
57
58 /**
59 * Timestamp displayed as a simple number
60 */
61 NUMBER,
62
63 /**
64 * Timestamp displayed as cycles
65 */
66 CYCLES
67 }
68
69 /**
70 * Timestamp resolution
71 */
72 public static enum Resolution {
73 /** seconds */
74 SECONDS,
75
76 /** milliseconds */
77 MILLISEC,
78
79 /** microseconds */
80 MICROSEC,
81
82 /** nanoseconds */
83 NANOSEC
84 }
85
86 /**
87 * Ellipsis character, used to shorten strings that don't fit in their
88 * target area.
89 *
90 * @since 2.1
91 */
92 public static final String ELLIPSIS = "…"; //$NON-NLS-1$
93
94 private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
95 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
96 private static final long HOURS_PER_DAY = 24;
97 private static final long MIN_PER_HOUR = 60;
98 private static final long SEC_PER_MIN = 60;
99 private static final long SEC_IN_NS = 1000000000;
100 private static final long MILLISEC_IN_NS = 1000000;
101
102 /**
103 * Update the time and date formats to use the current time zone
104 */
105 public static void updateTimeZone() {
106 TimeZone timeZone = TmfTimePreferences.getTimeZone();
107 TIME_FORMAT.setTimeZone(timeZone);
108 DATE_FORMAT.setTimeZone(timeZone);
109 }
110
111 static Rectangle clone(Rectangle source) {
112 return new Rectangle(source.x, source.y, source.width, source.height);
113 }
114
115 /**
116 * Initialize a Rectangle object to default values (all equal to 0)
117 *
118 * @param rect
119 * The Rectangle to initialize
120 */
121 public static void init(Rectangle rect) {
122 rect.x = 0;
123 rect.y = 0;
124 rect.width = 0;
125 rect.height = 0;
126 }
127
128 /**
129 * Initialize a Rectangle object with all the given values
130 *
131 * @param rect
132 * The Rectangle object to initialize
133 * @param x
134 * The X coordinate
135 * @param y
136 * The Y coordinate
137 * @param width
138 * The width of the rectangle
139 * @param height
140 * The height of the rectangle
141 */
142 public static void init(Rectangle rect, int x, int y, int width, int height) {
143 rect.x = x;
144 rect.y = y;
145 rect.width = width;
146 rect.height = height;
147 }
148
149 /**
150 * Initialize a Rectangle object to another existing Rectangle's values.
151 *
152 * @param rect
153 * The Rectangle to initialize
154 * @param source
155 * The reference Rectangle to copy
156 */
157 public static void init(Rectangle rect, Rectangle source) {
158 rect.x = source.x;
159 rect.y = source.y;
160 rect.width = source.width;
161 rect.height = source.height;
162 }
163
164 /**
165 * Reduce the size of a given rectangle by the given amounts.
166 *
167 * @param rect
168 * The rectangle to modify
169 * @param x
170 * The reduction in width
171 * @param y
172 * The reduction in height
173 */
174 public static void deflate(Rectangle rect, int x, int y) {
175 rect.x += x;
176 rect.y += y;
177 rect.width -= x + x;
178 rect.height -= y + y;
179 }
180
181 /**
182 * Increase the size of a given rectangle by the given amounts.
183 *
184 * @param rect
185 * The rectangle to modify
186 * @param x
187 * The augmentation in width
188 * @param y
189 * The augmentation in height
190 */
191 public static void inflate(Rectangle rect, int x, int y) {
192 rect.x -= x;
193 rect.y -= y;
194 rect.width += x + x;
195 rect.height += y + y;
196 }
197
198 static void dispose(Color col) {
199 if (null != col) {
200 col.dispose();
201 }
202 }
203
204 /**
205 * Get the resulting color from a mix of two existing ones for a given
206 * display.
207 *
208 * @param display
209 * The display device (which might affect the color conversion)
210 * @param c1
211 * The first color
212 * @param c2
213 * The second color
214 * @param w1
215 * The gamma level for color 1
216 * @param w2
217 * The gamma level for color 2
218 * @return The resulting color
219 */
220 public static Color mixColors(Device display, Color c1, Color c2, int w1,
221 int w2) {
222 return new Color(display, (w1 * c1.getRed() + w2 * c2.getRed())
223 / (w1 + w2), (w1 * c1.getGreen() + w2 * c2.getGreen())
224 / (w1 + w2), (w1 * c1.getBlue() + w2 * c2.getBlue())
225 / (w1 + w2));
226 }
227
228 /**
229 * Get the system color with the given ID.
230 *
231 * @param id
232 * The color ID
233 * @return The resulting color
234 */
235 public static Color getSysColor(int id) {
236 Color col = Display.getCurrent().getSystemColor(id);
237 return new Color(col.getDevice(), col.getRGB());
238 }
239
240 /**
241 * Get the resulting color from a mix of two existing ones for the current
242 * display.
243 *
244 * @param col1
245 * The first color
246 * @param col2
247 * The second color
248 * @param w1
249 * The gamma level for color 1
250 * @param w2
251 * The gamma level for color 2
252 * @return The resulting color
253 */
254 public static Color mixColors(Color col1, Color col2, int w1, int w2) {
255 return mixColors(Display.getCurrent(), col1, col2, w1, w2);
256 }
257
258 /**
259 * Draw text in a rectangle.
260 *
261 * @param gc
262 * The SWT GC object
263 * @param text
264 * The text to draw
265 * @param rect
266 * The rectangle object which is being drawn
267 * @param transp
268 * If true the background will be transparent
269 * @return The width of the written text
270 */
271 public static int drawText(GC gc, String text, Rectangle rect, boolean transp) {
272 Point size = gc.stringExtent(text);
273 gc.drawText(text, rect.x, rect.y, transp);
274 return size.x;
275 }
276
277 /**
278 * Draw text at a given location.
279 *
280 * @param gc
281 * The SWT GC object
282 * @param text
283 * The text to draw
284 * @param x
285 * The X coordinate of the starting point
286 * @param y
287 * the Y coordinate of the starting point
288 * @param transp
289 * If true the background will be transparent
290 * @return The width of the written text
291 */
292 public static int drawText(GC gc, String text, int x, int y, boolean transp) {
293 Point size = gc.stringExtent(text);
294 gc.drawText(text, x, y, transp);
295 return size.x;
296 }
297
298 /**
299 * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
300 *
301 * @param gc
302 * The SWT GC object
303 * @param text
304 * The string to be drawn
305 * @param x
306 * The x coordinate of the top left corner of the rectangular area where the text is to be drawn
307 * @param y
308 * The y coordinate of the top left corner of the rectangular area where the text is to be drawn
309 * @param width
310 * The width of the area to be drawn
311 * @param isCentered
312 * If <code>true</code> the text will be centered in the available width if space permits
313 * @param isTransparent
314 * If <code>true</code> the background will be transparent, otherwise it will be opaque
315 * @return The number of characters written
316 * @deprecated Use {@link #drawText(GC, String, int, int, int, int, boolean, boolean)} instead.
317 */
318 @Deprecated
319 public static int drawText(GC gc, String text, int x, int y, int width, boolean isCentered, boolean isTransparent) {
320 if (width < 1) {
321 return 0;
322 }
323
324 int len = text.length();
325 int textWidth = 0;
326 boolean isReallyCentered = isCentered;
327 int realX = x;
328
329 while (len > 0) {
330 textWidth = gc.stringExtent(text.substring(0, len)).x;
331 if (textWidth <= width) {
332 break;
333 }
334 isReallyCentered = false;
335 len--;
336 }
337 if (len > 0) {
338 if (isReallyCentered) {
339 realX += (width - textWidth) / 2;
340 }
341 gc.drawText(text.substring(0, len), realX, y, isTransparent);
342 }
343 return len;
344 }
345
346 /**
347 * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
348 *
349 * @param gc
350 * The SWT GC object
351 * @param text
352 * The string to be drawn
353 * @param x
354 * The x coordinate of the top left corner of the rectangular area where the text is to be drawn
355 * @param y
356 * The y coordinate of the top left corner of the rectangular area where the text is to be drawn
357 * @param width
358 * The width of the area to be drawn
359 * @param height
360 * The height of the area to be drawn
361 * @param isCentered
362 * If <code>true</code> the text will be centered in the available area if space permits
363 * @param isTransparent
364 * If <code>true</code> the background will be transparent, otherwise it will be opaque
365 * @return The number of characters written
366 * @since 2.0
367 */
368 public static int drawText(GC gc, String text, int x, int y, int width, int height, boolean isCentered, boolean isTransparent) {
369 if (width < 1 || text.isEmpty()) {
370 return 0;
371 }
372
373 String stringToDisplay;
374 int len;
375
376 boolean isCenteredWidth = isCentered;
377 int realX = x;
378 int realY = y;
379
380 /* First check if the whole string fits */
381 Point textExtent = gc.textExtent(text);
382 if (textExtent.x <= width) {
383 len = text.length();
384 stringToDisplay = text;
385 } else {
386 /*
387 * The full string doesn't fit, try to find the longest one with
388 * "..." at the end that does fit.
389 *
390 * Iterate on the string length sizes, starting from 1 going up,
391 * until we find a string that does not fit. Once we do, we keep the
392 * one just before that did fit.
393 */
394 isCenteredWidth = false;
395 int prevLen = 0;
396 len = 1;
397 while (len <= text.length()) {
398 textExtent = gc.textExtent(text.substring(0, len) + ELLIPSIS);
399 if (textExtent.x > width) {
400 /*
401 * Here is the first length that does not fit, the one from
402 * the previous iteration is the one we will use.
403 */
404 len = prevLen;
405 break;
406 }
407 /* This string would fit, try the next one */
408 prevLen = len;
409 len++;
410 }
411 stringToDisplay = text.substring(0, len) + ELLIPSIS;
412 }
413
414 if (len <= 0) {
415 /* Nothing fits, we end up drawing nothing */
416 return 0;
417 }
418
419 if (isCenteredWidth) {
420 realX += (width - textExtent.x) / 2;
421 }
422 if (isCentered) {
423 realY += (height - textExtent.y) / 2 - 1;
424 }
425 gc.drawText(stringToDisplay, realX, realY, isTransparent);
426
427 return len;
428 }
429
430 /**
431 * Formats time in format: MM:SS:NNN
432 *
433 * @param time time
434 * @param format 0: MMMM:ss:nnnnnnnnn, 1: HH:MM:ss MMM.mmmm.nnn
435 * @param resolution the resolution
436 * @return the formatted time
437 */
438 public static String formatTime(long time, TimeFormat format, Resolution resolution) {
439 switch (format) {
440 case CALENDAR:
441 return formatTimeAbs(time, resolution);
442 case NUMBER:
443 return NumberFormat.getInstance().format(time);
444 case CYCLES:
445 return NumberFormat.getInstance().format(time) + Messages.Utils_ClockCyclesUnit;
446 case RELATIVE:
447 default:
448 }
449
450 StringBuffer str = new StringBuffer();
451 long t = time;
452 boolean neg = t < 0;
453 if (neg) {
454 t = -t;
455 str.append('-');
456 }
457
458 long sec = t / SEC_IN_NS;
459 str.append(sec);
460 String ns = formatNs(t, resolution);
461 if (!ns.equals("")) { //$NON-NLS-1$
462 str.append('.');
463 str.append(ns);
464 }
465
466 return str.toString();
467 }
468
469 /**
470 * From input time in nanoseconds, convert to Date format YYYY-MM-dd
471 *
472 * @param absTime
473 * The source time, in ns
474 * @return the formatted date
475 */
476 public static String formatDate(long absTime) {
477 String sdate = DATE_FORMAT.format(new Date(absTime / MILLISEC_IN_NS));
478 return sdate;
479 }
480
481 /**
482 * Formats time in ns to Calendar format: HH:MM:SS MMM.mmm.nnn
483 *
484 * @param time
485 * The source time, in ns
486 * @param res
487 * The resolution to use
488 * @return the formatted time
489 */
490 public static String formatTimeAbs(long time, Resolution res) {
491 StringBuffer str = new StringBuffer();
492
493 // format time from nanoseconds to calendar time HH:MM:SS
494 String stime = TIME_FORMAT.format(new Date(time / MILLISEC_IN_NS));
495 str.append(stime);
496 str.append('.');
497 // append the Milliseconds, MicroSeconds and NanoSeconds as specified in
498 // the Resolution
499 str.append(formatNs(time, res));
500 return str.toString();
501 }
502
503 /**
504 * Formats time delta
505 *
506 * @param delta
507 * The time delta, in ns
508 * @param format
509 * The time format to use
510 * @param resolution
511 * The resolution to use
512 * @return the formatted time delta
513 */
514 public static String formatDelta(long delta, TimeFormat format, Resolution resolution) {
515 if (format == TimeFormat.CALENDAR) {
516 return formatDeltaAbs(delta, resolution);
517 }
518 return formatTime(delta, format, resolution);
519 }
520
521 /**
522 * Formats time delta in ns to Calendar format, only formatting the years,
523 * days, hours or minutes if necessary.
524 *
525 * @param delta
526 * The time delta, in ns
527 * @param resolution
528 * The resolution to use
529 * @return the formatted time delta
530 */
531 public static String formatDeltaAbs(long delta, Resolution resolution) {
532 StringBuffer str = new StringBuffer();
533 if (delta < 0) {
534 str.append('-');
535 }
536 long ns = Math.abs(delta);
537 long seconds = TimeUnit.NANOSECONDS.toSeconds(ns);
538 long minutes = TimeUnit.NANOSECONDS.toMinutes(ns);
539 long hours = TimeUnit.NANOSECONDS.toHours(ns);
540 long days = TimeUnit.NANOSECONDS.toDays(ns);
541 if (days > 0) {
542 str.append(days);
543 str.append("d "); //$NON-NLS-1$
544 }
545 if (hours > 0) {
546 str.append(hours % HOURS_PER_DAY);
547 str.append("h "); //$NON-NLS-1$
548 }
549 if (minutes > 0) {
550 str.append(minutes % MIN_PER_HOUR);
551 str.append("m "); //$NON-NLS-1$
552 }
553 str.append(seconds % SEC_PER_MIN);
554 str.append('.');
555 // append the ms, us and ns as specified in the resolution
556 str.append(formatNs(delta, resolution));
557 str.append("s"); //$NON-NLS-1$
558 return str.toString();
559 }
560
561 /**
562 * Obtains the remainder fraction on unit Seconds of the entered value in
563 * nanoseconds. e.g. input: 1241207054171080214 ns The number of fraction
564 * seconds can be obtained by removing the last 9 digits: 1241207054 the
565 * fractional portion of seconds, expressed in ns is: 171080214
566 *
567 * @param srcTime
568 * The source time in ns
569 * @param res
570 * The Resolution to use
571 * @return the formatted nanosec
572 */
573 public static String formatNs(long srcTime, Resolution res) {
574 StringBuffer str = new StringBuffer();
575 long ns = Math.abs(srcTime % SEC_IN_NS);
576 String nanos = Long.toString(ns);
577 str.append("000000000".substring(nanos.length())); //$NON-NLS-1$
578 str.append(nanos);
579
580 if (res == Resolution.MILLISEC) {
581 return str.substring(0, 3);
582 } else if (res == Resolution.MICROSEC) {
583 return str.substring(0, 6);
584 } else if (res == Resolution.NANOSEC) {
585 return str.substring(0, 9);
586 }
587 return ""; //$NON-NLS-1$
588 }
589
590 /**
591 * FIXME Currently does nothing.
592 *
593 * @param opt
594 * The option name
595 * @param def
596 * The option value
597 * @param min
598 * The minimal accepted value
599 * @param max
600 * The maximal accepted value
601 * @return The value that was read
602 */
603 public static int loadIntOption(String opt, int def, int min, int max) {
604 return def;
605 }
606
607 /**
608 * FIXME currently does nothing
609 *
610 * @param opt
611 * The option name
612 * @param val
613 * The option value
614 */
615 public static void saveIntOption(String opt, int val) {
616 }
617
618 static ITimeEvent getFirstEvent(ITimeGraphEntry entry) {
619 if (null == entry || ! entry.hasTimeEvents()) {
620 return null;
621 }
622 Iterator<? extends ITimeEvent> iterator = entry.getTimeEventsIterator();
623 if (iterator != null && iterator.hasNext()) {
624 return iterator.next();
625 }
626 return null;
627 }
628
629 /**
630 * Gets the {@link ITimeEvent} at the given time from the given
631 * {@link ITimeGraphEntry}.
632 *
633 * @param entry
634 * a {@link ITimeGraphEntry}
635 * @param time
636 * a timestamp
637 * @param n
638 * this parameter means: <list> <li>-1: Previous Event</li> <li>
639 * 0: Current Event</li> <li>
640 * 1: Next Event</li> <li>2: Previous Event when located in a non
641 * Event Area </list>
642 * @return a {@link ITimeEvent}, or <code>null</code>.
643 */
644 public static ITimeEvent findEvent(ITimeGraphEntry entry, long time, int n) {
645 if (null == entry || ! entry.hasTimeEvents()) {
646 return null;
647 }
648 Iterator<@NonNull ? extends ITimeEvent> iterator = entry.getTimeEventsIterator();
649 if (iterator == null) {
650 return null;
651 }
652 ITimeEvent nextEvent = null;
653 ITimeEvent currEvent = null;
654 ITimeEvent prevEvent = null;
655
656 while (iterator.hasNext()) {
657 nextEvent = iterator.next();
658 long nextStartTime = nextEvent.getTime();
659
660 if (nextStartTime > time) {
661 break;
662 }
663
664 if (currEvent == null || currEvent.getTime() != nextStartTime ||
665 (nextStartTime != time && currEvent.getDuration() != nextEvent.getDuration())) {
666 prevEvent = currEvent;
667 currEvent = nextEvent;
668 }
669 }
670
671 if (n == -1) { //previous
672 if (currEvent != null && currEvent.getTime() + currEvent.getDuration() >= time) {
673 return prevEvent;
674 }
675 return currEvent;
676 } else if (n == 0) { //current
677 if (currEvent != null && currEvent.getTime() + currEvent.getDuration() >= time) {
678 return currEvent;
679 }
680 return null;
681 } else if (n == 1) { //next
682 if (nextEvent != null && nextEvent.getTime() > time) {
683 return nextEvent;
684 }
685 return null;
686 } else if (n == 2) { //current or previous when in empty space
687 return currEvent;
688 }
689
690 return null;
691 }
692
693 /**
694 * Pretty-print a method signature.
695 *
696 * @param origSig
697 * The original signature
698 * @return The pretty signature
699 */
700 public static String fixMethodSignature(String origSig) {
701 String sig = origSig;
702 int pos = sig.indexOf('(');
703 if (pos >= 0) {
704 String ret = sig.substring(0, pos);
705 sig = sig.substring(pos);
706 sig = sig + " " + ret; //$NON-NLS-1$
707 }
708 return sig;
709 }
710
711 /**
712 * Restore an original method signature from a pretty-printed one.
713 *
714 * @param ppSig
715 * The pretty-printed signature
716 * @return The original method signature
717 */
718 public static String restoreMethodSignature(String ppSig) {
719 String ret = ""; //$NON-NLS-1$
720 String sig = ppSig;
721
722 int pos = sig.indexOf('(');
723 if (pos >= 0) {
724 ret = sig.substring(0, pos);
725 sig = sig.substring(pos + 1);
726 }
727 pos = sig.indexOf(')');
728 if (pos >= 0) {
729 sig = sig.substring(0, pos);
730 }
731 String args[] = sig.split(","); //$NON-NLS-1$
732 StringBuffer result = new StringBuffer("("); //$NON-NLS-1$
733 for (int i = 0; i < args.length; i++) {
734 String arg = args[i].trim();
735 if (arg.length() == 0 && args.length == 1) {
736 break;
737 }
738 result.append(getTypeSignature(arg));
739 }
740 result.append(")").append(getTypeSignature(ret)); //$NON-NLS-1$
741 return result.toString();
742 }
743
744 /**
745 * Get the mangled type information from an array of types.
746 *
747 * @param typeStr
748 * The types to convert. See method implementation for what it
749 * expects.
750 * @return The mangled string of types
751 */
752 public static String getTypeSignature(String typeStr) {
753 int dim = 0;
754 String type = typeStr;
755 for (int j = 0; j < type.length(); j++) {
756 if (type.charAt(j) == '[') {
757 dim++;
758 }
759 }
760 int pos = type.indexOf('[');
761 if (pos >= 0) {
762 type = type.substring(0, pos);
763 }
764 StringBuffer sig = new StringBuffer(""); //$NON-NLS-1$
765 for (int j = 0; j < dim; j++)
766 {
767 sig.append("["); //$NON-NLS-1$
768 }
769 if (type.equals("boolean")) { //$NON-NLS-1$
770 sig.append('Z');
771 } else if (type.equals("byte")) { //$NON-NLS-1$
772 sig.append('B');
773 } else if (type.equals("char")) { //$NON-NLS-1$
774 sig.append('C');
775 } else if (type.equals("short")) { //$NON-NLS-1$
776 sig.append('S');
777 } else if (type.equals("int")) { //$NON-NLS-1$
778 sig.append('I');
779 } else if (type.equals("long")) { //$NON-NLS-1$
780 sig.append('J');
781 } else if (type.equals("float")) { //$NON-NLS-1$
782 sig.append('F');
783 } else if (type.equals("double")) { //$NON-NLS-1$
784 sig.append('D');
785 } else if (type.equals("void")) { //$NON-NLS-1$
786 sig.append('V');
787 }
788 else {
789 sig.append('L').append(type.replace('.', '/')).append(';');
790 }
791 return sig.toString();
792 }
793
794 /**
795 * Compare two doubles together.
796 *
797 * @param d1
798 * First double
799 * @param d2
800 * Second double
801 * @return 1 if they are different, and 0 if they are *exactly* the same.
802 * Because of the way doubles are stored, it's possible for the
803 * same number obtained in two different ways to actually look
804 * different.
805 */
806 public static int compare(double d1, double d2) {
807 if (d1 > d2) {
808 return 1;
809 }
810 if (d1 < d2) {
811 return 1;
812 }
813 return 0;
814 }
815
816 /**
817 * Compare two character strings alphabetically. This is simply a wrapper
818 * around String.compareToIgnoreCase but that will handle cases where
819 * strings can be null
820 *
821 * @param s1
822 * The first string
823 * @param s2
824 * The second string
825 * @return A number below, equal, or greater than zero if the first string
826 * is smaller, equal, or bigger (alphabetically) than the second
827 * one.
828 */
829 public static int compare(String s1, String s2) {
830 if (s1 != null && s2 != null) {
831 return s1.compareToIgnoreCase(s2);
832 }
833 if (s1 != null) {
834 return 1;
835 }
836 if (s2 != null) {
837 return -1;
838 }
839 return 0;
840 }
841
842 /**
843 * Calculates the square of the distance between two points.
844 *
845 * @param x1
846 * x-coordinate of point 1
847 * @param y1
848 * y-coordinate of point 1
849 * @param x2
850 * x-coordinate of point 2
851 * @param y2
852 * y-coordinate of point 2
853 *
854 * @return the square of the distance in pixels^2
855 */
856 public static double distance2(int x1, int y1, int x2, int y2) {
857 int dx = x2 - x1;
858 int dy = y2 - y1;
859 int d2 = dx * dx + dy * dy;
860 return d2;
861 }
862
863 /**
864 * Calculates the distance between a point and a line segment. If the point
865 * is in the perpendicular region between the segment points, return the
866 * distance from the point to its projection on the segment. Otherwise
867 * return the distance from the point to its closest segment point.
868 *
869 * @param px
870 * x-coordinate of the point
871 * @param py
872 * y-coordinate of the point
873 * @param x1
874 * x-coordinate of segment point 1
875 * @param y1
876 * y-coordinate of segment point 1
877 * @param x2
878 * x-coordinate of segment point 2
879 * @param y2
880 * y-coordinate of segment point 2
881 *
882 * @return the distance in pixels
883 */
884 public static double distance(int px, int py, int x1, int y1, int x2, int y2) {
885 double length2 = distance2(x1, y1, x2, y2);
886 if (length2 == 0) {
887 return Math.sqrt(distance2(px, py, x1, y1));
888 }
889 // 'r' is the ratio of the position, between segment point 1 and segment
890 // point 2, of the projection of the point on the segment
891 double r = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / length2;
892 if (r <= 0.0) {
893 // the projection is before segment point 1, return distance from
894 // the point to segment point 1
895 return Math.sqrt(distance2(px, py, x1, y1));
896 }
897 if (r >= 1.0) {
898 // the projection is after segment point 2, return distance from
899 // the point to segment point 2
900 return Math.sqrt(distance2(px, py, x2, y2));
901 }
902 // the projection is between the segment points, return distance from
903 // the point to its projection on the segment
904 int x = (int) (x1 + r * (x2 - x1));
905 int y = (int) (y1 + r * (y2 - y1));
906 return Math.sqrt(distance2(px, py, x, y));
907 }
908 }
This page took 0.05255 seconds and 5 git commands to generate.