tmf: Keep focused item aligned on vertical zoom
authorPatrick Tasse <patrick.tasse@gmail.com>
Mon, 1 Feb 2016 20:45:37 +0000 (15:45 -0500)
committerPatrick Tasse <patrick.tasse@gmail.com>
Tue, 2 Feb 2016 22:05:25 +0000 (17:05 -0500)
When doing a vertical zoom with the keyboard, if the selected entry is
visible, make sure that it remains at the same vertical position.

When doing a vertical zoom with the mouse wheel, make sure that the
entry under the mouse cursor remains at the same vertical position.

When repeatedly doing a vertical zoom within a short amount of time, use
the initial event's entry and position for the subsequent alignments.

Fix the method getItemIndexAtY() to find items that are above the
visible bounds.

Change-Id: I803229c5cf5295b855d4615a6756751158b78346
Signed-off-by: Patrick Tasse <patrick.tasse@gmail.com>
Reviewed-on: https://git.eclipse.org/r/65605
Reviewed-by: Hudson CI
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/TimeGraphCombo.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java

index 4ff736cc93c994c891a7e19cc2f435962a429587..33fdfb4dc91b42465d6c3b707e5f60c2b41a467c 100644 (file)
@@ -43,6 +43,7 @@ import org.eclipse.swt.events.ControlAdapter;
 import org.eclipse.swt.events.ControlEvent;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseTrackAdapter;
 import org.eclipse.swt.events.MouseWheelListener;
@@ -177,6 +178,18 @@ public class TimeGraphCombo extends Composite {
                 public void resetVerticalZoom() {
                     TimeGraphCombo.this.resetVerticalZoom();
                 }
+
+                @Override
+                public void setElementPosition(ITimeGraphEntry entry, int y) {
+                    /*
+                     * Queue the update to make sure the time graph combo has finished
+                     * updating the item heights.
+                     */
+                    getDisplay().asyncExec(() -> {
+                        super.setElementPosition(entry, y);
+                        alignTreeItems(false);
+                    });
+                }
             };
         }
     }
@@ -563,11 +576,14 @@ public class TimeGraphCombo extends Composite {
                     fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(treeItems.size() - 1).getData());
                     event.doit = false;
                 } else if ((event.character == '+' || event.character == '=') && ((event.stateMask & SWT.CTRL) != 0)) {
-                    verticalZoom(true);
+                    fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+                    return;
                 } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
-                    verticalZoom(false);
+                    fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+                    return;
                 } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
-                    resetVerticalZoom();
+                    fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
+                    return;
                 } else {
                     return;
                 }
index 13438c859bb5988f436a8425747a0b4aa3555e21..bb991f6bafba59548bb112002d22e8ea5ac7f3ef 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
 
+import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -28,6 +29,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jface.action.IStatusLineManager;
@@ -126,6 +128,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private static final int PPI = 72; // points per inch
     private static final int DPI = Display.getDefault().getDPI().y;
 
+    private static final int VERTICAL_ZOOM_DELAY = 400;
+
     /** Resource manager */
     private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
 
@@ -173,7 +177,8 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
     private boolean fHideArrows = false;
     private int fAutoExpandLevel = ALL_LEVELS;
-
+    private Entry<ITimeGraphEntry, Integer> fVerticalZoomAlignEntry = null;
+    private long fVerticalZoomAlignTime = 0;
     private int fBorderWidth = 0;
     private int fHeaderHeight = 0;
 
@@ -483,6 +488,36 @@ public class TimeGraphControl extends TimeGraphBaseControl
         redraw();
     }
 
+    /**
+     * Set the top index so that the requested element is at the specified
+     * position.
+     *
+     * @param entry
+     *            the time graph entry to be positioned
+     * @param y
+     *            the requested y-coordinate
+     * @since 2.0
+     */
+    public void setElementPosition(ITimeGraphEntry entry, int y) {
+        Item item = fItemData.fItemMap.get(entry);
+        if (item == null || item.fExpandedIndex == -1) {
+            return;
+        }
+        int index = item.fExpandedIndex;
+        Rectangle itemRect = getItemRect(getClientArea(), index);
+        int delta = itemRect.y + itemRect.height - y;
+        int topIndex = getItemIndexAtY(delta);
+        if (topIndex != -1) {
+            setTopIndex(topIndex);
+        } else {
+            if (delta < 0) {
+                setTopIndex(0);
+            } else {
+                setTopIndex(getExpandedElementCount());
+            }
+        }
+    }
+
     /**
      * Sets the auto-expand level to be used for new entries discovered when
      * calling {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}
@@ -1211,14 +1246,20 @@ public class TimeGraphControl extends TimeGraphBaseControl
      * @return the index of the item at the given location, of -1 if none.
      */
     protected int getItemIndexAtY(int y) {
-        if (y < 0) {
-            return -1;
-        }
         int ySum = 0;
-        for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
-            ySum += fItemData.fExpandedItems[idx].fItemHeight;
-            if (y < ySum) {
-                return idx;
+        if (y < 0) {
+            for (int idx = fTopIndex - 1; idx >= 0; idx--) {
+                ySum -= fItemData.fExpandedItems[idx].fItemHeight;
+                if (y >= ySum) {
+                    return idx;
+                }
+            }
+        } else {
+            for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
+                ySum += fItemData.fExpandedItems[idx].fItemHeight;
+                if (y < ySum) {
+                    return idx;
+                }
             }
         }
         return -1;
@@ -1410,6 +1451,21 @@ public class TimeGraphControl extends TimeGraphBaseControl
         return elements.toArray(new ITimeGraphEntry[0]);
     }
 
+    /**
+     * Get the expanded (visible) element at the specified index.
+     *
+     * @param index
+     *            the element index
+     * @return The expanded (visible) element or null if out of range
+     * @since 2.0
+     */
+    public ITimeGraphEntry getExpandedElement(int index) {
+        if (index < 0 || index >= fItemData.fExpandedItems.length) {
+            return null;
+        }
+        return fItemData.fExpandedItems[index].fEntry;
+    }
+
     Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
         Rectangle rect = getItemRect(bounds, idx);
         rect.width = nameWidth;
@@ -2222,11 +2278,23 @@ public class TimeGraphControl extends TimeGraphBaseControl
             }
             idx = -1;
         } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
             verticalZoom(true);
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
         } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
             verticalZoom(false);
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
         } else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
             resetVerticalZoom();
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
         }
         if (idx >= 0) {
             selectItem(idx, false);
@@ -2672,11 +2740,15 @@ public class TimeGraphControl extends TimeGraphBaseControl
             }
         }
         if (verticalZoom) {
+            fVerticalZoomAlignEntry = getVerticalZoomAlignCursor(e.y);
             if (e.count > 0) {
                 verticalZoom(true);
             } else if (e.count < 0) {
                 verticalZoom(false);
             }
+            if (fVerticalZoomAlignEntry != null) {
+                setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
+            }
         } else if (horizontalZoom && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
             if (e.count > 0) {
                 zoom(true);
@@ -2690,6 +2762,77 @@ public class TimeGraphControl extends TimeGraphBaseControl
         }
     }
 
+    /**
+     * Get the vertical zoom alignment entry and position based on the current
+     * selection. If there is no selection or if the selection is not visible,
+     * return an alignment entry with a null time graph entry.
+     *
+     * @return a map entry where the key is the selection's time graph entry and
+     *         the value is the center y-coordinate of that entry, or null
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignSelection() {
+        Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
+        if (alignEntry != null) {
+            return alignEntry;
+        }
+        int index = getSelectedIndex();
+        if (index == -1 || index >= getExpandedElementCount()) {
+            return new SimpleEntry<>(null, 0);
+        }
+        Rectangle bounds = getClientArea();
+        Rectangle itemRect = getItemRect(bounds, index);
+        if (itemRect.y < bounds.y || itemRect.y > bounds.y + bounds.height) {
+            /* selection is not visible */
+            return new SimpleEntry<>(null, 0);
+        }
+        ITimeGraphEntry entry = getExpandedElement(index);
+        int y = itemRect.y + itemRect.height / 2;
+        return new SimpleEntry<>(entry, y);
+    }
+
+    /**
+     * Get the vertical zoom alignment entry and position at the specified
+     * cursor position.
+     *
+     * @param y
+     *            the cursor y-coordinate
+     * @return a map entry where the key is the time graph entry under the
+     *         cursor and the value is the cursor y-coordinate
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignCursor(int y) {
+        Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
+        if (alignEntry != null) {
+            return alignEntry;
+        }
+        int index = getItemIndexAtY(y);
+        if (index == -1) {
+            index = getExpandedElementCount() - 1;
+        }
+        ITimeGraphEntry entry = getExpandedElement(index);
+        return new SimpleEntry<>(entry, y);
+    }
+
+    /**
+     * Get the vertical zoom alignment entry and position if there is an ongoing
+     * one and we are within the vertical zoom delay, or otherwise return null.
+     *
+     * @return a map entry where the key is a time graph entry and the value is
+     *         a y-coordinate, or null
+     */
+    private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignOngoing() {
+        long currentTimeMillis = System.currentTimeMillis();
+        if (currentTimeMillis < fVerticalZoomAlignTime + VERTICAL_ZOOM_DELAY) {
+            /*
+             * If the vertical zoom is triggered repeatedly in a short amount of
+             * time, use the initial event's entry and position.
+             */
+            fVerticalZoomAlignTime = currentTimeMillis;
+            return fVerticalZoomAlignEntry;
+        }
+        fVerticalZoomAlignTime = currentTimeMillis;
+        return null;
+    }
+
     @Override
     public void handleEvent(Event event) {
         if (event.type == SWT.MouseWheel) {
This page took 0.030074 seconds and 5 git commands to generate.