1 /*****************************************************************************
2 * Copyright (c) 2007, 2017 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
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 *****************************************************************************/
16 package org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
;
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
;
25 import org
.eclipse
.jdt
.annotation
.NonNull
;
26 import org
.eclipse
.swt
.SWT
;
27 import org
.eclipse
.swt
.graphics
.Color
;
28 import org
.eclipse
.swt
.graphics
.Device
;
29 import org
.eclipse
.swt
.graphics
.GC
;
30 import org
.eclipse
.swt
.graphics
.Point
;
31 import org
.eclipse
.swt
.graphics
.RGB
;
32 import org
.eclipse
.swt
.graphics
.Rectangle
;
33 import org
.eclipse
.swt
.widgets
.Display
;
34 import org
.eclipse
.tracecompass
.common
.core
.format
.DecimalUnitFormat
;
35 import org
.eclipse
.tracecompass
.internal
.tmf
.ui
.Messages
;
36 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimePreferences
;
37 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeEvent
;
38 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
41 * General utilities and definitions used by the time graph widget
43 * @author Alvaro Sanchez-Leon
44 * @author Patrick Tasse
51 /** Time format for dates and timestamp */
52 public enum TimeFormat
{
53 /** Relative to the start of the trace */
57 * Absolute timestamp (ie, relative to the Unix epoch)
62 * Timestamp displayed as a simple number
67 * Timestamp displayed as cycles
73 * Timestamp resolution
75 public static enum Resolution
{
90 * Ellipsis character, used to shorten strings that don't fit in their
95 public static final String ELLIPSIS
= "…"; //$NON-NLS-1$
97 private static final SimpleDateFormat TIME_FORMAT
= new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
98 private static final SimpleDateFormat DATE_FORMAT
= new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
99 private static final long HOURS_PER_DAY
= 24;
100 private static final long MIN_PER_HOUR
= 60;
101 private static final long SEC_PER_MIN
= 60;
102 private static final long SEC_IN_NS
= 1000000000;
103 private static final long MILLISEC_IN_NS
= 1000000;
106 * Update the time and date formats to use the current time zone
108 public static void updateTimeZone() {
109 TimeZone timeZone
= TmfTimePreferences
.getTimeZone();
110 TIME_FORMAT
.setTimeZone(timeZone
);
111 DATE_FORMAT
.setTimeZone(timeZone
);
114 static Rectangle
clone(Rectangle source
) {
115 return new Rectangle(source
.x
, source
.y
, source
.width
, source
.height
);
119 * Initialize a Rectangle object to default values (all equal to 0)
122 * The Rectangle to initialize
124 public static void init(Rectangle rect
) {
132 * Initialize a Rectangle object with all the given values
135 * The Rectangle object to initialize
141 * The width of the rectangle
143 * The height of the rectangle
145 public static void init(Rectangle rect
, int x
, int y
, int width
, int height
) {
149 rect
.height
= height
;
153 * Initialize a Rectangle object to another existing Rectangle's values.
156 * The Rectangle to initialize
158 * The reference Rectangle to copy
160 public static void init(Rectangle rect
, Rectangle source
) {
163 rect
.width
= source
.width
;
164 rect
.height
= source
.height
;
168 * Reduce the size of a given rectangle by the given amounts.
171 * The rectangle to modify
173 * The reduction in width
175 * The reduction in height
177 public static void deflate(Rectangle rect
, int x
, int y
) {
181 rect
.height
-= y
+ y
;
185 * Increase the size of a given rectangle by the given amounts.
188 * The rectangle to modify
190 * The augmentation in width
192 * The augmentation in height
194 public static void inflate(Rectangle rect
, int x
, int y
) {
198 rect
.height
+= y
+ y
;
201 static void dispose(Color col
) {
208 * Get the resulting color from a mix of two existing ones for a given
212 * The display device (which might affect the color conversion)
218 * The gamma level for color 1
220 * The gamma level for color 2
221 * @return The resulting color
223 public static Color
mixColors(Device display
, Color c1
, Color c2
, int w1
,
225 return new Color(display
, (w1
* c1
.getRed() + w2
* c2
.getRed())
226 / (w1
+ w2
), (w1
* c1
.getGreen() + w2
* c2
.getGreen())
227 / (w1
+ w2
), (w1
* c1
.getBlue() + w2
* c2
.getBlue())
232 * Get the system color with the given ID.
236 * @return The resulting color
238 public static Color
getSysColor(int id
) {
239 Color col
= Display
.getCurrent().getSystemColor(id
);
240 return new Color(col
.getDevice(), col
.getRGB());
244 * Get the resulting color from a mix of two existing ones for the current
252 * The gamma level for color 1
254 * The gamma level for color 2
255 * @return The resulting color
257 public static Color
mixColors(Color col1
, Color col2
, int w1
, int w2
) {
258 return mixColors(Display
.getCurrent(), col1
, col2
, w1
, w2
);
262 * Get a distinct color from the specified RGB color, based on its
263 * relative luminance.
267 * @return The black or white system color, whichever is more distinct.
270 public static Color
getDistinctColor(RGB rgb
) {
271 /* Calculate the relative luminance of the color, high value is bright */
272 final int luminanceThreshold
= 128;
273 /* Relative luminance (Y) coefficients as defined in ITU.R Rec. 709 */
274 final double redCoefficient
= 0.2126;
275 final double greenCoefficient
= 0.7152;
276 final double blueCoefficient
= 0.0722;
277 int luminance
= (int) (redCoefficient
* rgb
.red
+ greenCoefficient
* rgb
.green
+ blueCoefficient
* rgb
.blue
);
278 /* Use black over bright colors and white over dark colors */
279 return Display
.getDefault().getSystemColor(
280 luminance
> luminanceThreshold ? SWT
.COLOR_BLACK
: SWT
.COLOR_WHITE
);
284 * Draw text in a rectangle.
291 * The rectangle object which is being drawn
293 * If true the background will be transparent
294 * @return The width of the written text
296 public static int drawText(GC gc
, String text
, Rectangle rect
, boolean transp
) {
297 Point size
= gc
.stringExtent(text
);
298 gc
.drawText(text
, rect
.x
, rect
.y
, transp
);
303 * Draw text at a given location.
310 * The X coordinate of the starting point
312 * the Y coordinate of the starting point
314 * If true the background will be transparent
315 * @return The width of the written text
317 public static int drawText(GC gc
, String text
, int x
, int y
, boolean transp
) {
318 Point size
= gc
.stringExtent(text
);
319 gc
.drawText(text
, x
, y
, transp
);
324 * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
329 * The string to be drawn
331 * The x coordinate of the top left corner of the rectangular area where the text is to be drawn
333 * The y coordinate of the top left corner of the rectangular area where the text is to be drawn
335 * The width of the area to be drawn
337 * If <code>true</code> the text will be centered in the available width if space permits
338 * @param isTransparent
339 * If <code>true</code> the background will be transparent, otherwise it will be opaque
340 * @return The number of characters written
341 * @deprecated Use {@link #drawText(GC, String, int, int, int, int, boolean, boolean)} instead.
344 public static int drawText(GC gc
, String text
, int x
, int y
, int width
, boolean isCentered
, boolean isTransparent
) {
349 int len
= text
.length();
351 boolean isReallyCentered
= isCentered
;
355 textWidth
= gc
.stringExtent(text
.substring(0, len
)).x
;
356 if (textWidth
<= width
) {
359 isReallyCentered
= false;
363 if (isReallyCentered
) {
364 realX
+= (width
- textWidth
) / 2;
366 gc
.drawText(text
.substring(0, len
), realX
, y
, isTransparent
);
372 * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
377 * The string to be drawn
379 * The x coordinate of the top left corner of the rectangular area where the text is to be drawn
381 * The y coordinate of the top left corner of the rectangular area where the text is to be drawn
383 * The width of the area to be drawn
385 * The height of the area to be drawn
387 * If <code>true</code> the text will be centered in the available area if space permits
388 * @param isTransparent
389 * If <code>true</code> the background will be transparent, otherwise it will be opaque
390 * @return The number of characters written
393 public static int drawText(GC gc
, String text
, int x
, int y
, int width
, int height
, boolean isCentered
, boolean isTransparent
) {
394 if (width
< 1 || text
.isEmpty()) {
398 String stringToDisplay
;
401 boolean isCenteredWidth
= isCentered
;
405 /* First check if the whole string fits */
406 Point textExtent
= gc
.textExtent(text
);
407 if (textExtent
.x
<= width
) {
409 stringToDisplay
= text
;
412 * The full string doesn't fit, try to find the longest one with
413 * "..." at the end that does fit.
415 * Iterate on the string length sizes, starting from 1 going up,
416 * until we find a string that does not fit. Once we do, we keep the
417 * one just before that did fit.
419 isCenteredWidth
= false;
422 while (len
<= text
.length()) {
423 textExtent
= gc
.textExtent(text
.substring(0, len
) + ELLIPSIS
);
424 if (textExtent
.x
> width
) {
426 * Here is the first length that does not fit, the one from
427 * the previous iteration is the one we will use.
432 /* This string would fit, try the next one */
436 stringToDisplay
= text
.substring(0, len
) + ELLIPSIS
;
440 /* Nothing fits, we end up drawing nothing */
444 if (isCenteredWidth
) {
445 realX
+= (width
- textExtent
.x
) / 2;
448 realY
+= (height
- textExtent
.y
) / 2 - 1;
450 gc
.drawText(stringToDisplay
, realX
, realY
, isTransparent
);
456 * Formats time in format: MM:SS:NNN
459 * @param format 0: MMMM:ss:nnnnnnnnn, 1: HH:MM:ss MMM.mmmm.nnn
460 * @param resolution the resolution
461 * @return the formatted time
463 public static String
formatTime(long time
, TimeFormat format
, Resolution resolution
) {
466 return formatTimeAbs(time
, resolution
);
468 return NumberFormat
.getInstance().format(time
);
470 return NumberFormat
.getInstance().format(time
) + Messages
.Utils_ClockCyclesUnit
;
475 StringBuffer str
= new StringBuffer();
483 long sec
= t
/ SEC_IN_NS
;
485 String ns
= formatNs(t
, resolution
);
486 if (!ns
.equals("")) { //$NON-NLS-1$
491 return str
.toString();
495 * From input time in nanoseconds, convert to Date format YYYY-MM-dd
498 * The source time, in ns
499 * @return the formatted date
501 public static String
formatDate(long absTime
) {
502 String sdate
= DATE_FORMAT
.format(new Date(absTime
/ MILLISEC_IN_NS
));
507 * Formats time in ns to Calendar format: HH:MM:SS MMM.mmm.nnn
510 * The source time, in ns
512 * The resolution to use
513 * @return the formatted time
515 public static String
formatTimeAbs(long time
, Resolution res
) {
516 StringBuffer str
= new StringBuffer();
518 // format time from nanoseconds to calendar time HH:MM:SS
519 String stime
= TIME_FORMAT
.format(new Date(time
/ MILLISEC_IN_NS
));
521 String ns
= formatNs(time
, res
);
525 * append the Milliseconds, MicroSeconds and NanoSeconds as
526 * specified in the Resolution
530 return str
.toString();
537 * The time delta, in ns
539 * The time format to use
541 * The resolution to use
542 * @return the formatted time delta
544 public static String
formatDelta(long delta
, TimeFormat format
, Resolution resolution
) {
545 if (format
== TimeFormat
.CALENDAR
) {
546 return formatDeltaAbs(delta
, resolution
);
548 return formatTime(delta
, format
, resolution
);
552 * Formats time delta in ns to Calendar format, only formatting the years,
553 * days, hours or minutes if necessary.
556 * The time delta, in ns
558 * The resolution to use
559 * @return the formatted time delta
561 public static String
formatDeltaAbs(long delta
, Resolution resolution
) {
562 StringBuffer str
= new StringBuffer();
566 long ns
= Math
.abs(delta
);
567 long seconds
= TimeUnit
.NANOSECONDS
.toSeconds(ns
);
568 long minutes
= TimeUnit
.NANOSECONDS
.toMinutes(ns
);
569 long hours
= TimeUnit
.NANOSECONDS
.toHours(ns
);
570 long days
= TimeUnit
.NANOSECONDS
.toDays(ns
);
573 str
.append("d "); //$NON-NLS-1$
576 str
.append(hours
% HOURS_PER_DAY
);
577 str
.append("h "); //$NON-NLS-1$
580 str
.append(minutes
% MIN_PER_HOUR
);
581 str
.append("m "); //$NON-NLS-1$
583 str
.append(seconds
% SEC_PER_MIN
);
585 // append the ms, us and ns as specified in the resolution
586 str
.append(formatNs(delta
, resolution
));
587 str
.append("s"); //$NON-NLS-1$
589 str
.append(" ("); //$NON-NLS-1$
590 str
.append(new DecimalUnitFormat(1.0 / SEC_IN_NS
).format(delta
));
591 str
.append("s)"); //$NON-NLS-1$
593 return str
.toString();
597 * Obtains the remainder fraction on unit Seconds of the entered value in
598 * nanoseconds. e.g. input: 1241207054171080214 ns The number of fraction
599 * seconds can be obtained by removing the last 9 digits: 1241207054 the
600 * fractional portion of seconds, expressed in ns is: 171080214
603 * The source time in ns
605 * The Resolution to use
606 * @return the formatted nanosec
608 public static String
formatNs(long srcTime
, Resolution res
) {
609 StringBuffer str
= new StringBuffer();
610 long ns
= Math
.abs(srcTime
% SEC_IN_NS
);
611 String nanos
= Long
.toString(ns
);
612 str
.append("000000000".substring(nanos
.length())); //$NON-NLS-1$
615 if (res
== Resolution
.MILLISEC
) {
616 return str
.substring(0, 3);
617 } else if (res
== Resolution
.MICROSEC
) {
618 return str
.substring(0, 6);
619 } else if (res
== Resolution
.NANOSEC
) {
620 return str
.substring(0, 9);
622 return ""; //$NON-NLS-1$
626 * FIXME Currently does nothing.
633 * The minimal accepted value
635 * The maximal accepted value
636 * @return The value that was read
638 public static int loadIntOption(String opt
, int def
, int min
, int max
) {
643 * FIXME currently does nothing
650 public static void saveIntOption(String opt
, int val
) {
653 static ITimeEvent
getFirstEvent(ITimeGraphEntry entry
) {
654 if (null == entry
|| ! entry
.hasTimeEvents()) {
657 Iterator
<?
extends ITimeEvent
> iterator
= entry
.getTimeEventsIterator();
658 if (iterator
!= null && iterator
.hasNext()) {
659 return iterator
.next();
665 * Gets the {@link ITimeEvent} at the given time from the given
666 * {@link ITimeGraphEntry}.
669 * a {@link ITimeGraphEntry}
673 * this parameter means: <list> <li>-1: Previous Event</li> <li>
674 * 0: Current Event</li> <li>
675 * 1: Next Event</li> <li>2: Previous Event when located in a non
677 * @return a {@link ITimeEvent}, or <code>null</code>.
679 public static ITimeEvent
findEvent(ITimeGraphEntry entry
, long time
, int n
) {
680 if (null == entry
|| ! entry
.hasTimeEvents()) {
683 Iterator
<@NonNull ?
extends ITimeEvent
> iterator
= entry
.getTimeEventsIterator();
684 if (iterator
== null) {
687 ITimeEvent nextEvent
= null;
688 ITimeEvent currEvent
= null;
689 ITimeEvent prevEvent
= null;
691 while (iterator
.hasNext()) {
692 nextEvent
= iterator
.next();
693 long nextStartTime
= nextEvent
.getTime();
695 if (nextStartTime
> time
) {
699 if (currEvent
== null || currEvent
.getTime() != nextStartTime
||
700 (nextStartTime
!= time
&& currEvent
.getDuration() != nextEvent
.getDuration())) {
701 prevEvent
= currEvent
;
702 currEvent
= nextEvent
;
706 if (n
== -1) { //previous
707 if (currEvent
!= null && currEvent
.getTime() + currEvent
.getDuration() >= time
) {
711 } else if (n
== 0) { //current
712 if (currEvent
!= null && currEvent
.getTime() + currEvent
.getDuration() >= time
) {
716 } else if (n
== 1) { //next
717 if (nextEvent
!= null && nextEvent
.getTime() > time
) {
721 } else if (n
== 2) { //current or previous when in empty space
729 * Pretty-print a method signature.
732 * The original signature
733 * @return The pretty signature
735 public static String
fixMethodSignature(String origSig
) {
736 String sig
= origSig
;
737 int pos
= sig
.indexOf('(');
739 String ret
= sig
.substring(0, pos
);
740 sig
= sig
.substring(pos
);
741 sig
= sig
+ " " + ret
; //$NON-NLS-1$
747 * Restore an original method signature from a pretty-printed one.
750 * The pretty-printed signature
751 * @return The original method signature
753 public static String
restoreMethodSignature(String ppSig
) {
754 String ret
= ""; //$NON-NLS-1$
757 int pos
= sig
.indexOf('(');
759 ret
= sig
.substring(0, pos
);
760 sig
= sig
.substring(pos
+ 1);
762 pos
= sig
.indexOf(')');
764 sig
= sig
.substring(0, pos
);
766 String args
[] = sig
.split(","); //$NON-NLS-1$
767 StringBuffer result
= new StringBuffer("("); //$NON-NLS-1$
768 for (int i
= 0; i
< args
.length
; i
++) {
769 String arg
= args
[i
].trim();
770 if (arg
.length() == 0 && args
.length
== 1) {
773 result
.append(getTypeSignature(arg
));
775 result
.append(")").append(getTypeSignature(ret
)); //$NON-NLS-1$
776 return result
.toString();
780 * Get the mangled type information from an array of types.
783 * The types to convert. See method implementation for what it
785 * @return The mangled string of types
787 public static String
getTypeSignature(String typeStr
) {
789 String type
= typeStr
;
790 for (int j
= 0; j
< type
.length(); j
++) {
791 if (type
.charAt(j
) == '[') {
795 int pos
= type
.indexOf('[');
797 type
= type
.substring(0, pos
);
799 StringBuffer sig
= new StringBuffer(""); //$NON-NLS-1$
800 for (int j
= 0; j
< dim
; j
++)
802 sig
.append("["); //$NON-NLS-1$
804 if (type
.equals("boolean")) { //$NON-NLS-1$
806 } else if (type
.equals("byte")) { //$NON-NLS-1$
808 } else if (type
.equals("char")) { //$NON-NLS-1$
810 } else if (type
.equals("short")) { //$NON-NLS-1$
812 } else if (type
.equals("int")) { //$NON-NLS-1$
814 } else if (type
.equals("long")) { //$NON-NLS-1$
816 } else if (type
.equals("float")) { //$NON-NLS-1$
818 } else if (type
.equals("double")) { //$NON-NLS-1$
820 } else if (type
.equals("void")) { //$NON-NLS-1$
824 sig
.append('L').append(type
.replace('.', '/')).append(';');
826 return sig
.toString();
830 * Compare two doubles together.
836 * @return 1 if they are different, and 0 if they are *exactly* the same.
837 * Because of the way doubles are stored, it's possible for the
838 * same number obtained in two different ways to actually look
841 public static int compare(double d1
, double d2
) {
852 * Compare two character strings alphabetically. This is simply a wrapper
853 * around String.compareToIgnoreCase but that will handle cases where
854 * strings can be null
860 * @return A number below, equal, or greater than zero if the first string
861 * is smaller, equal, or bigger (alphabetically) than the second
864 public static int compare(String s1
, String s2
) {
865 if (s1
!= null && s2
!= null) {
866 return s1
.compareToIgnoreCase(s2
);
878 * Calculates the square of the distance between two points.
881 * x-coordinate of point 1
883 * y-coordinate of point 1
885 * x-coordinate of point 2
887 * y-coordinate of point 2
889 * @return the square of the distance in pixels^2
891 public static double distance2(int x1
, int y1
, int x2
, int y2
) {
894 int d2
= dx
* dx
+ dy
* dy
;
899 * Calculates the distance between a point and a line segment. If the point
900 * is in the perpendicular region between the segment points, return the
901 * distance from the point to its projection on the segment. Otherwise
902 * return the distance from the point to its closest segment point.
905 * x-coordinate of the point
907 * y-coordinate of the point
909 * x-coordinate of segment point 1
911 * y-coordinate of segment point 1
913 * x-coordinate of segment point 2
915 * y-coordinate of segment point 2
917 * @return the distance in pixels
919 public static double distance(int px
, int py
, int x1
, int y1
, int x2
, int y2
) {
920 double length2
= distance2(x1
, y1
, x2
, y2
);
922 return Math
.sqrt(distance2(px
, py
, x1
, y1
));
924 // 'r' is the ratio of the position, between segment point 1 and segment
925 // point 2, of the projection of the point on the segment
926 double r
= ((px
- x1
) * (x2
- x1
) + (py
- y1
) * (y2
- y1
)) / length2
;
928 // the projection is before segment point 1, return distance from
929 // the point to segment point 1
930 return Math
.sqrt(distance2(px
, py
, x1
, y1
));
933 // the projection is after segment point 2, return distance from
934 // the point to segment point 2
935 return Math
.sqrt(distance2(px
, py
, x2
, y2
));
937 // the projection is between the segment points, return distance from
938 // the point to its projection on the segment
939 int x
= (int) (x1
+ r
* (x2
- x1
));
940 int y
= (int) (y1
+ r
* (y2
- y1
));
941 return Math
.sqrt(distance2(px
, py
, x
, y
));