package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
/**
*/
public class TimeGraphMarkerAxis extends TimeGraphBaseControl {
+ private static final Image COLLAPSED = Activator.getDefault().getImageFromPath("icons/ovr16/collapsed_ovr.gif"); //$NON-NLS-1$
+ private static final Image EXPANDED = Activator.getDefault().getImageFromPath("icons/ovr16/expanded_ovr.gif"); //$NON-NLS-1$
+ private static final Image HIDE = Activator.getDefault().getImageFromPath("icons/etool16/hide.gif"); //$NON-NLS-1$
+ private static final int HIDE_BORDER = 4; // transparent border of the hide icon
+
private static final int HEIGHT;
static {
GC gc = new GC(Display.getDefault());
private static final int X_LIMIT = Integer.MAX_VALUE / 256;
private @NonNull ITimeDataProvider fTimeProvider;
+ private final Set<IMarkerAxisListener> fListeners = new LinkedHashSet<>();
private Multimap<String, IMarkerEvent> fMarkers = LinkedHashMultimap.create();
- private List<String> fCategories = Collections.EMPTY_LIST;
+ private @NonNull List<String> fCategories = Collections.EMPTY_LIST;
+ private boolean fCollapsed = false;
/**
* Contructor
addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
- IMarkerEvent marker = getMarkerForEvent(e);
- if (marker != null) {
- fTimeProvider.setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
- }
+ Point size = getSize();
+ Rectangle bounds = new Rectangle(0, 0, size.x, size.y);
+ TimeGraphMarkerAxis.this.mouseDown(e, bounds, fTimeProvider.getNameSpace());
}
});
}
public Point computeSize(int wHint, int hHint, boolean changed) {
int height = 0;
if (!fMarkers.isEmpty() && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
- height = TOP_MARGIN + fMarkers.keySet().size() * HEIGHT;
+ if (fCollapsed) {
+ height = COLLAPSED.getBounds().height;
+ } else {
+ height = TOP_MARGIN + fMarkers.keySet().size() * HEIGHT;
+ }
}
return super.computeSize(wHint, height, changed);
}
+ /**
+ * Add a marker axis listener.
+ *
+ * @param listener
+ * the listener
+ */
+ public void addMarkerAxisListener(IMarkerAxisListener listener) {
+ fListeners.add(listener);
+ }
+
+ /**
+ * Remove a marker axis listener.
+ *
+ * @param listener
+ * the listener
+ */
+ public void removeMarkerAxisListener(IMarkerAxisListener listener) {
+ fListeners.remove(listener);
+ }
+
/**
* Set the time provider
*
fTimeProvider = timeProvider;
}
+ /**
+ * Set the list of marker categories.
+ *
+ * @param categories
+ * The list of marker categories, or null
+ */
+ public void setMarkerCategories(List<String> categories) {
+ if (categories == null) {
+ fCategories = Collections.EMPTY_LIST;
+ } else {
+ fCategories = categories;
+ }
+ }
+
+ /**
+ * Handle a mouseDown event.
+ *
+ * @param e
+ * the mouse event
+ * @param bounds
+ * the bounds of the marker axis in the mouse event's coordinates
+ * @param nameSpace
+ * the width of the marker name area
+ */
+ public void mouseDown(MouseEvent e, Rectangle bounds, int nameSpace) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+ if (fCollapsed || (e.x < bounds.x + Math.min(nameSpace, EXPANDED.getBounds().width))) {
+ fCollapsed = !fCollapsed;
+ getParent().layout();
+ redraw();
+ return;
+ }
+ if (e.x < bounds.x + nameSpace) {
+ String category = getHiddenCategoryForEvent(e, bounds);
+ if (category != null) {
+ for (IMarkerAxisListener listener : fListeners) {
+ listener.setMarkerCategoryVisible(category, false);
+ }
+ }
+ return;
+ }
+ IMarkerEvent marker = getMarkerForEvent(e);
+ if (marker != null) {
+ fTimeProvider.setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
+ }
+ }
+
/**
* Set the markers list.
*
for (IMarkerEvent marker : markers) {
map.put(marker.getCategory(), marker);
}
- List<String> categories = Lists.newArrayList(map.keySet());
- Collections.sort(categories);
Display.getDefault().asyncExec(() -> {
+ if (isDisposed()) {
+ return;
+ }
fMarkers = map;
- fCategories = categories;
getParent().layout();
redraw();
});
* the GC instance
*/
protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
+ if (bounds.isEmpty()) {
+ return;
+ }
// draw background
gc.fillRectangle(bounds);
- Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT);
- for (String category : fCategories) {
- rect.x = bounds.x;
- rect.width = nameSpace;
- drawMarkerCategory(category, rect, gc);
- rect.x = nameSpace;
- rect.width = bounds.width - nameSpace;
- drawMarkerLabels(category, rect, gc);
- rect.y += HEIGHT;
+ if (!fCollapsed) {
+ Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT);
+ for (String category : getVisibleCategories()) {
+ rect.x = bounds.x;
+ rect.width = nameSpace;
+ drawMarkerCategory(category, rect, gc);
+ rect.x = nameSpace;
+ rect.width = bounds.width - nameSpace;
+ drawMarkerLabels(category, rect, gc);
+ rect.y += HEIGHT;
+ }
}
+
+ Rectangle rect = new Rectangle(bounds.x, bounds.y, nameSpace, bounds.height);
+ gc.setClipping(rect);
+ drawToolbar(rect, nameSpace, gc);
}
/**
gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
gc.setClipping(rect);
int width = gc.textExtent(category).x + TEXT_MARGIN;
- gc.drawText(category, Math.max(rect.x, rect.x + rect.width - width), rect.y, true);
+ int x = rect.x + EXPANDED.getBounds().width + HIDE.getBounds().width;
+ gc.drawText(category, Math.max(x, rect.x + rect.width - width), rect.y, true);
}
/**
for (IMarkerEvent markerEvent : fMarkers.get(category)) {
Color color = getColorScheme().getColor(markerEvent.getColor());
gc.setForeground(color);
+ gc.setBackground(color);
int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime());
if (x1 > rect.x + rect.width) {
return;
}
}
gc.fillRectangle(x1, rect.y, width, rect.height - 1);
- Utils.drawText(gc, label, x1 + TEXT_MARGIN, rect.y, true);
gc.drawRectangle(x1, rect.y, width, rect.height - 1);
if (x2 > x1 + width) {
int y = rect.y + rect.height / 2;
gc.drawLine(x1 + width, y, x2, y);
}
+ gc.setForeground(getDistinctForeground(color.getRGB()));
+ Utils.drawText(gc, label, x1 + TEXT_MARGIN, rect.y, true);
} else {
int y = rect.y + rect.height / 2;
gc.drawLine(x1, y, x2, y);
}
}
+ private static Color getDistinctForeground(RGB rgb) {
+ /* Calculate the relative luminance of the color, high value is bright */
+ final int luminanceThreshold = 128;
+ /* Relative luminance (Y) coefficients as defined in ITU.R Rec. 709 */
+ final double redCoefficient = 0.2126;
+ final double greenCoefficient = 0.7152;
+ final double blueCoefficient = 0.0722;
+ int luminance = (int) (redCoefficient * rgb.red + greenCoefficient * rgb.green + blueCoefficient * rgb.blue);
+ /* Use black over bright colors and white over dark colors */
+ return Display.getDefault().getSystemColor(
+ luminance > luminanceThreshold ? SWT.COLOR_BLACK : SWT.COLOR_WHITE);
+ }
+
+ /**
+ * Draw the toolbar
+ *
+ * @param bounds
+ * the bounds of the marker axis
+ * @param nameSpace
+ * the width of the marker name area
+ * @param gc
+ * the GC instance
+ */
+ protected void drawToolbar(Rectangle bounds, int nameSpace, GC gc) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+ if (fCollapsed) {
+ gc.drawImage(COLLAPSED, bounds.x, bounds.y);
+ } else {
+ gc.drawImage(EXPANDED, bounds.x, bounds.y);
+ int x = bounds.x + EXPANDED.getBounds().width;
+ for (int i = 0; i < fMarkers.keySet().size(); i++) {
+ int y = bounds.y + TOP_MARGIN + i * HEIGHT;
+ gc.drawImage(HIDE, x, y);
+ }
+ }
+ }
+
private static String getTrimmedLabel(IMarkerEvent marker) {
String label = marker.getLabel();
if (label == null) {
(double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
int categoryIndex = Math.max((event.y - TOP_MARGIN) / HEIGHT, 0);
- String category = fCategories.get(categoryIndex);
+ String category = getVisibleCategories().get(categoryIndex);
IMarkerEvent marker = null;
GC gc = new GC(Display.getDefault());
gc.dispose();
return marker;
}
+
+ private String getHiddenCategoryForEvent(MouseEvent e, Rectangle bounds) {
+ List<String> categories = getVisibleCategories();
+ Rectangle rect = HIDE.getBounds();
+ rect.x += bounds.x + EXPANDED.getBounds().width + HIDE_BORDER;
+ rect.y += bounds.y + TOP_MARGIN + HIDE_BORDER;
+ rect.width -= 2 * HIDE_BORDER;
+ rect.height -= 2 * HIDE_BORDER;
+ for (int i = 0; i < categories.size(); i++) {
+ if (rect.contains(e.x, e.y)) {
+ return categories.get(i);
+ }
+ rect.y += HEIGHT;
+ }
+ return null;
+ }
+
+ private List<String> getVisibleCategories() {
+ List<String> categories = new ArrayList<>(fCategories);
+ categories.retainAll(fMarkers.keySet());
+ return categories;
+ }
}