tmf: Add vertical zooming support to time graph viewer and combo
authorPatrick Tasse <patrick.tasse@gmail.com>
Tue, 8 Dec 2015 14:48:36 +0000 (09:48 -0500)
committerPatrick Tasse <patrick.tasse@gmail.com>
Thu, 10 Dec 2015 22:26:18 +0000 (17:26 -0500)
Vertical zoom is triggered with Ctrl+"+" and Ctrl+"-". Reset is
triggered by Ctrl+"0".

Ctrl+"=" is equivalent to Ctrl+"+" for vertical zoom-in, and "=" is now
equivalent to "+" for horizontal zoom-in also.

The font of the time graph control is changed along with the zooming,
and the views with post-draw handling to draw labels on time graph items
are modified to use a new method which centers the text vertically.

Change-Id: Ic75c1ebe7e937c612b64346e2814894c500f964f
Signed-off-by: Patrick Tasse <patrick.tasse@gmail.com>
Reviewed-on: https://git.eclipse.org/r/62359
Reviewed-by: Hudson CI
Reviewed-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Tested-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
analysis/org.eclipse.tracecompass.analysis.os.linux.ui/src/org/eclipse/tracecompass/analysis/os/linux/ui/views/controlflow/ControlFlowPresentationProvider.java
analysis/org.eclipse.tracecompass.analysis.os.linux.ui/src/org/eclipse/tracecompass/analysis/os/linux/ui/views/resources/ResourcesPresentationProvider.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/views/callstack/CallStackPresentationProvider.java
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/TimeGraphViewer.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/Utils.java

index f61d87b46ca79003ff4f5f474b9943aef2df37ab..367532743a9e0bf91edb456198b7bd4b36e3727a 100644 (file)
@@ -231,7 +231,7 @@ public class ControlFlowPresentationProvider extends TimeGraphPresentationProvid
                     beginIndex = layout.eventSyscallEntryPrefix().length();
                 }
 
-                Utils.drawText(gc, state.toString().substring(beginIndex), bounds.x, bounds.y - 2, bounds.width, true, true);
+                Utils.drawText(gc, state.toString().substring(beginIndex), bounds.x, bounds.y, bounds.width, bounds.height, true, true);
             }
         } catch (AttributeNotFoundException | TimeRangeException e) {
             Activator.getDefault().logError("Error in ControlFlowPresentationProvider", e); //$NON-NLS-1$
index a0ac390572512768beaa28168701323a386fd24f..a88767605bdb2acb6837c3e5f9b9bad041ae8e3c 100644 (file)
@@ -349,7 +349,7 @@ public class ResourcesPresentationProvider extends TimeGraphPresentationProvider
                                 if (!interval.getStateValue().isNull()) {
                                     value = interval.getStateValue();
                                     gc.setForeground(fColorWhite);
-                                    int drawn = Utils.drawText(gc, value.unboxStr().substring(beginIndex), x + 1, bounds.y - 2, width, true, true);
+                                    int drawn = Utils.drawText(gc, value.unboxStr().substring(beginIndex), x + 1, bounds.y, width, bounds.height, true, true);
                                     if (drawn > 0) {
                                         fLastThreadId = currentThreadId;
                                     }
index 8b3ff894722e0abb3e97ebb335a0ef43aef1a6c4..991e625fa8343fe7b601e4c46713e63fc8705d11 100644 (file)
@@ -146,7 +146,7 @@ public class CallStackPresentationProvider extends TimeGraphPresentationProvider
                 String address = value.toString();
                 String name = fView.getFunctionName(address);
                 gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE));
-                Utils.drawText(gc, name, bounds.x, bounds.y - 2, bounds.width, true, true);
+                Utils.drawText(gc, name, bounds.x, bounds.y, bounds.width, bounds.height, true, true);
             }
         } catch (AttributeNotFoundException e) {
             Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$
index 7d855d3251e791ce212c18cd1ac96c625347e1eb..71d42fa368020492b61f1e0f8906c5e49d05a615 100644 (file)
@@ -41,6 +41,8 @@ import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.SashForm;
 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.MouseEvent;
 import org.eclipse.swt.events.MouseTrackAdapter;
 import org.eclipse.swt.events.MouseWheelListener;
@@ -48,6 +50,8 @@ import org.eclipse.swt.events.PaintEvent;
 import org.eclipse.swt.events.PaintListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
@@ -71,6 +75,8 @@ import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ITimeGraphEntry
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.dialogs.ShowFilterDialogAction;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
 
 import com.google.common.collect.Iterables;
 
@@ -133,10 +139,62 @@ public class TimeGraphCombo extends Composite {
 
     private final boolean fScrollBarsInTreeWorkaround;
 
+    private Font fTreeFont;
+
     // ------------------------------------------------------------------------
     // Classes
     // ------------------------------------------------------------------------
 
+    /**
+     * The TimeGraphViewerExtension is used to set appropriate values and to
+     * override methods that could be called directly by the user and that must
+     * be handled by the time graph combo.
+     */
+    private class TimeGraphViewerExtension extends TimeGraphViewer {
+
+        private TimeGraphViewerExtension(Composite parent, int style, Tree tree) {
+            super(parent, style);
+            setItemHeight(TimeGraphCombo.this.getItemHeight(tree, true));
+            setHeaderHeight(tree.getHeaderHeight());
+            setBorderWidth(tree.getBorderWidth());
+            setNameWidthPref(0);
+        }
+
+        @Override
+        public ShowFilterDialogAction getShowFilterDialogAction() {
+            return TimeGraphCombo.this.getShowFilterDialogAction();
+        }
+
+        @Override
+        protected TimeGraphControl createTimeGraphControl(Composite composite, TimeGraphColorScheme colors) {
+            return new TimeGraphControl(composite, colors) {
+                @Override
+                public void verticalZoom(boolean zoomIn, boolean adjustItems) {
+                    boolean changed = TimeGraphCombo.this.verticalZoom(zoomIn);
+                    if (changed) {
+                        /*
+                         * The time graph combo takes care of adjusting item
+                         * heights, only adjust the font in the time graph
+                         * control. Only do it if the time graph combo's tree
+                         * font has actually changed.
+                         */
+                        super.verticalZoom(zoomIn, false);
+                    }
+                }
+
+                @Override
+                public void resetVerticalZoom(boolean adjustItems) {
+                    TimeGraphCombo.this.resetVerticalZoom();
+                    /*
+                     * The time graph combo takes care of resetting item
+                     * heights, only reset the font in the time graph control.
+                     */
+                    super.resetVerticalZoom(false);
+                }
+            };
+        }
+    }
+
     /**
      * The TreeContentProviderWrapper is used to insert filler items after
      * the elements of the tree's real content provider.
@@ -358,11 +416,7 @@ public class TimeGraphCombo extends Composite {
         tree.setHeaderVisible(true);
         tree.setLinesVisible(true);
 
-        fTimeGraphViewer = new TimeGraphViewer(fSashForm, SWT.NONE);
-        fTimeGraphViewer.setItemHeight(getItemHeight(tree));
-        fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
-        fTimeGraphViewer.setBorderWidth(tree.getBorderWidth());
-        fTimeGraphViewer.setNameWidthPref(0);
+        fTimeGraphViewer = new TimeGraphViewerExtension(fSashForm, SWT.NONE, tree);
 
         if (fScrollBarsInTreeWorkaround) {
             // Feature in Windows. The tree vertical bar reappears when
@@ -397,6 +451,15 @@ public class TimeGraphCombo extends Composite {
             }
         });
 
+        tree.addDisposeListener(new DisposeListener() {
+            @Override
+            public void widgetDisposed(DisposeEvent e) {
+                if (fTreeFont != null) {
+                    fTreeFont.dispose();
+                }
+            }
+        });
+
         // ensure synchronization of expanded items between tree and time graph
         fTreeViewer.addTreeListener(new ITreeViewerListener() {
             @Override
@@ -506,13 +569,21 @@ public class TimeGraphCombo extends Composite {
                     event.doit = false;
                 } else if (event.keyCode == SWT.PAGE_DOWN) {
                     int height = tree.getSize().y - tree.getHeaderHeight() - tree.getHorizontalBar().getSize().y;
-                    int countPerPage = height / getItemHeight(tree);
+                    int countPerPage = height / getItemHeight(tree, false);
                     int index = Math.min(fTimeGraphViewer.getSelectionIndex() + countPerPage - 1, treeItems.size() - 1);
                     fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
                     event.doit = false;
                 } else if (event.keyCode == SWT.END) {
                     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);
+                } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
+                    verticalZoom(false);
+                } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
+                    resetVerticalZoom();
+                } else {
+                    return;
                 }
                 if (fTimeGraphViewer.getSelectionIndex() >= 0) {
                     fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
@@ -612,7 +683,7 @@ public class TimeGraphCombo extends Composite {
         // The filler rows are required to ensure alignment when the tree does not have a
         // visible horizontal scroll bar. The tree does not allow its top item to be set
         // to a value that would cause blank space to be drawn at the bottom of the tree.
-        fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree);
+        fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree, false);
 
         fSashForm.setWeights(weights);
 
@@ -643,6 +714,43 @@ public class TimeGraphCombo extends Composite {
         });
     }
 
+    private boolean verticalZoom(boolean zoomIn) {
+        Tree tree = fTreeViewer.getTree();
+        FontData fontData = tree.getFont().getFontData()[0];
+        int height = fontData.getHeight() + (zoomIn ? 1 : -1);
+        if (height <= 0) {
+            return false;
+        }
+        fontData.setHeight(height);
+        if (fTreeFont != null) {
+            fTreeFont.dispose();
+        }
+        fTreeFont = new Font(tree.getDisplay(), fontData);
+        tree.setFont(fTreeFont);
+        redraw();
+        update();
+        fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
+        fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
+        alignTreeItems(false);
+        redraw();
+        return true;
+    }
+
+    private void resetVerticalZoom() {
+        Tree tree = fTreeViewer.getTree();
+        if (fTreeFont != null) {
+            fTreeFont.dispose();
+            fTreeFont = null;
+        }
+        tree.setFont(null);
+        redraw();
+        update();
+        fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
+        fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
+        alignTreeItems(false);
+        redraw();
+    }
+
     private void sendTimeViewAlignmentChanged() {
         TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(fSashForm, getTimeViewAlignmentInfo()));
     }
@@ -710,6 +818,12 @@ public class TimeGraphCombo extends Composite {
         super.redraw();
     }
 
+    @Override
+    public void update() {
+        fTimeGraphViewer.getControl().update();
+        super.update();
+    }
+
     // ------------------------------------------------------------------------
     // Operations
     // ------------------------------------------------------------------------
@@ -831,7 +945,7 @@ public class TimeGraphCombo extends Composite {
             fTreeViewer.getTree().getVerticalBar().setVisible(false);
         }
         fTimeGraphViewer.setInput(input);
-        fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree()));
+        fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree(), false));
         // queue the alignment update because in Linux the item bounds are not
         // set properly until the tree has been painted at least once
         fVisibleExpandedItems = null; // invalidate the cache
@@ -1077,12 +1191,12 @@ public class TimeGraphCombo extends Composite {
         }
     }
 
-    private int getItemHeight(final Tree tree) {
+    private int getItemHeight(final Tree tree, boolean force) {
         /*
          * Bug in Linux.  The method getItemHeight doesn't always return the correct value.
          */
         if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
-            if (fLinuxItemHeight != 0) {
+            if (fLinuxItemHeight != 0 && !force) {
                 return fLinuxItemHeight;
             }
 
@@ -1126,29 +1240,63 @@ public class TimeGraphCombo extends Composite {
         TreeItem item = treeItems.get(topIndex);
         tree.setTopItem(item);
 
+        // get the first filler item so we can calculate the last item's height
+        TreeItem fillerItem = null;
+        for (TreeItem treeItem : fTreeViewer.getTree().getItems()) {
+            if (treeItem.getData() == FILLER) {
+                fillerItem = treeItem;
+                break;
+            }
+        }
+
         // ensure the time graph item heights are equal to the tree item heights
         int treeHeight = fTreeViewer.getTree().getBounds().height;
         int index = topIndex;
         Rectangle bounds = item.getBounds();
-        while (index < treeItems.size() - 1) {
+        while (index < treeItems.size()) {
             if (bounds.y > treeHeight) {
                 break;
             }
-            /*
-             * Bug in Linux. The method getBounds doesn't always return the correct height.
-             * Use the difference of y position between items to calculate the height.
-             */
-            TreeItem nextItem = treeItems.get(index + 1);
-            Rectangle nextBounds = nextItem.getBounds();
-            Integer itemHeight = nextBounds.y - bounds.y;
-            if (itemHeight > 0) {
-                ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
-                fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight);
-            }
+            TreeItem nextItem = (index + 1 == treeItems.size()) ? fillerItem : treeItems.get(index + 1);
+            Rectangle nextBounds = alignTreeItem(item, bounds, nextItem);
             index++;
             item = nextItem;
             bounds = nextBounds;
         }
+
+        /*
+         * When an item's height in the time graph changes, it is possible that
+         * the time graph readjusts its top index to fill empty space at the
+         * bottom of the viewer. Calling method setTopIndex() triggers this
+         * adjustment, if needed. In that case, we need to make sure that the
+         * newly visible items at the top of the viewer are also aligned.
+         */
+        fTimeGraphViewer.setTopIndex(topIndex);
+        item = treeItems.get(topIndex);
+        tree.setTopItem(item);
+        while (fTimeGraphViewer.getTopIndex() < topIndex) {
+            TreeItem nextItem = item;
+            topIndex--;
+            item = treeItems.get(topIndex);
+            tree.setTopItem(item);
+            bounds = item.getBounds();
+            alignTreeItem(item, bounds, nextItem);
+            fTimeGraphViewer.setTopIndex(topIndex);
+        }
+    }
+
+    private Rectangle alignTreeItem(TreeItem item, Rectangle bounds, TreeItem nextItem) {
+        /*
+         * Bug in Linux. The method getBounds doesn't always return the correct height.
+         * Use the difference of y position between items to calculate the height.
+         */
+        Rectangle nextBounds = nextItem.getBounds();
+        Integer itemHeight = nextBounds.y - bounds.y;
+        if (itemHeight > 0) {
+            ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
+            fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight);
+        }
+        return nextBounds;
     }
 
     /**
index 5bdb90dd1ca504fcd3f8a9b5175e1a720a4be61d..3fd9d3c36d7b95fa831fd3feea99ebfa316fc04f 100644 (file)
@@ -525,9 +525,9 @@ public class TimeGraphViewer implements ITimeDataProvider, SelectionListener {
         fTimeGraphCtrl.addKeyListener(new KeyAdapter() {
             @Override
             public void keyPressed(KeyEvent e) {
-                if (e.character == '+') {
+                if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) == 0)) {
                     zoomIn();
-                } else if (e.character == '-') {
+                } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) == 0)) {
                     zoomOut();
                 } else if (e.keyCode == '.') {
                     boolean extend = (e.stateMask & SWT.SHIFT) != 0;
index 5a1e37d1dacfd6b2d34cf860640d9dcc63d5ae62..826259545a22f15761e98182da8d935a6dbdcee6 100644 (file)
@@ -57,6 +57,8 @@ import org.eclipse.swt.events.TraverseListener;
 import org.eclipse.swt.events.TypedEvent;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
@@ -133,6 +135,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
     private boolean fIsInFocus = false;
     private boolean fMouseOverSplitLine = false;
     private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT;
+    private int fHeightAdjustment = 0;
+    private int fInitialFontHeight;
+    private Font fFont;
     private boolean fBlendSubPixelEvents = false;
     private int fMinimumItemWidth = 0;
     private int fTopIndex = 0;
@@ -198,6 +203,9 @@ public class TimeGraphControl extends TimeGraphBaseControl
     public void dispose() {
         super.dispose();
         fResourceManager.dispose();
+        if (fFont != null) {
+            fFont.dispose();
+        }
     }
 
     /**
@@ -957,6 +965,58 @@ public class TimeGraphControl extends TimeGraphBaseControl
         fTimeProvider.setStartFinishTimeNotify(time0, time1);
     }
 
+    /**
+     * Zoom vertically.
+     *
+     * @param zoomIn
+     *            true to zoom in, false to zoom out
+     * @param adjustItems
+     *            true to adjust item heights, false to adjust font only
+     * @since 2.0
+     */
+    public void verticalZoom(boolean zoomIn, boolean adjustItems) {
+        if (zoomIn) {
+            fHeightAdjustment++;
+        } else {
+            fHeightAdjustment--;
+        }
+        FontData fontData = getFont().getFontData()[0];
+        if (fInitialFontHeight == 0) {
+            fInitialFontHeight = fontData.getHeight();
+        }
+        int height = Math.max(1, fInitialFontHeight + fHeightAdjustment);
+        fontData.setHeight(height);
+        if (fFont != null) {
+            fFont.dispose();
+        }
+        fFont = new Font(getDisplay(), fontData);
+        setFont(fFont);
+        if (adjustItems) {
+            fItemData.refreshData();
+        }
+        redraw();
+    }
+
+    /**
+     * Reset the vertical zoom to default.
+     *
+     * @param adjustItems
+     *            true to reset item heights, false to reset font only
+     * @since 2.0
+     */
+    public void resetVerticalZoom(boolean adjustItems) {
+        fHeightAdjustment = 0;
+        if (fFont != null) {
+            fFont.dispose();
+            fFont = null;
+        }
+        setFont(null);
+        if (adjustItems) {
+            fItemData.refreshData();
+        }
+        redraw();
+    }
+
     /**
      * Set the grid lines visibility. The default is true.
      *
@@ -2151,6 +2211,12 @@ public class TimeGraphControl extends TimeGraphBaseControl
                 }
             }
             idx = -1;
+        } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) {
+            verticalZoom(true, true);
+        } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) {
+            verticalZoom(false, true);
+        } else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) {
+            resetVerticalZoom(true);
         }
         if (idx >= 0) {
             selectItem(idx, false);
@@ -2850,6 +2916,7 @@ public class TimeGraphControl extends TimeGraphBaseControl
             } else {
                 item.fItemHeight = fGlobalItemHeight;
             }
+            item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
             itemMap.put(entry, item);
             if (entry.hasChildren()) {
                 Item oldItem = fItemMap.get(entry);
index 94d2cf6f7dc0d169ea668d3db0a761e7986b6a2e..6881af53c87a6db613688bb104b9ee5e1499bf63 100644 (file)
@@ -332,6 +332,62 @@ public class Utils {
         return len;
     }
 
+    /**
+     * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
+     *
+     * @param gc
+     *            The SWT GC object
+     * @param text
+     *            The string to be drawn
+     * @param x
+     *            The x coordinate of the top left corner of the rectangular area where the text is to be drawn
+     * @param y
+     *            The y coordinate of the top left corner of the rectangular area where the text is to be drawn
+     * @param width
+     *            The width of the area to be drawn
+     * @param height
+     *            The height of the area to be drawn
+     * @param isCentered
+     *            If <code>true</code> the text will be centered in the available area if space permits
+     * @param isTransparent
+     *            If <code>true</code> the background will be transparent, otherwise it will be opaque
+     * @return The number of characters written
+     * @since 2.0
+     */
+    public static int drawText(GC gc, String text, int x, int y, int width, int height, boolean isCentered, boolean isTransparent) {
+        if (width < 1) {
+            return 0;
+        }
+
+        int len = text.length();
+        int textWidth = 0;
+        boolean isCenteredWidth = isCentered;
+        int realX = x;
+        int realY = y;
+
+        Point textExtent = null;
+        while (len > 0) {
+            textExtent = gc.textExtent(text.substring(0, len));
+            textWidth = textExtent.x;
+            if (textWidth <= width) {
+                break;
+            }
+            isCenteredWidth = false;
+            len--;
+            textExtent = gc.textExtent(text.substring(0, len));
+        }
+        if (len > 0) {
+            if (isCenteredWidth) {
+                realX += (width - textWidth) / 2;
+            }
+            if (isCentered && textExtent != null) {
+                realY += (height - textExtent.y) / 2 - 1;
+            }
+            gc.drawText(text.substring(0, len), realX, realY, isTransparent);
+        }
+        return len;
+    }
+
     /**
      * Formats time in format: MM:SS:NNN
      *
This page took 0.034665 seconds and 5 git commands to generate.