From 8411dc738efbb38da8c56fe1a997c0f46e3463b5 Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Wed, 14 May 2025 11:57:00 +0200 Subject: [PATCH] Fit Table Tooltip in a single monitor for Win32 This commit contributes to the fitting of the table tooltip inside a single monitor if it spans over multiple monitor to avoid any infinite loop because of rescaling triggered by any DPI_CHANGED events. contributes to https://github.com/eclipse-platform/eclipse.platform.swt/issues/62 and https://github.com/eclipse-platform/eclipse.platform.swt/issues/128 --- .../swt/widgets/CoordinateSystemMapper.java | 2 + .../org/eclipse/swt/widgets/Display.java | 19 +++ .../MultiZoomCoordinateSystemMapper.java | 8 ++ .../SingleZoomCoordinateSystemMapper.java | 13 ++ .../win32/org/eclipse/swt/widgets/Table.java | 131 +++++++++--------- .../win32/org/eclipse/swt/widgets/Tree.java | 52 +------ 6 files changed, 114 insertions(+), 111 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoordinateSystemMapper.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoordinateSystemMapper.java index 28527ed39e4..652bcba719c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoordinateSystemMapper.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoordinateSystemMapper.java @@ -35,6 +35,8 @@ interface CoordinateSystemMapper { Rectangle translateToDisplayCoordinates(Rectangle rect, int zoom); + Rectangle getContainingMonitorBoundsInPixels(Point point); + void setCursorLocation(int x, int y); Point getCursorLocation(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java index 766f9176730..b559f11b534 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java @@ -1724,6 +1724,25 @@ public Point getCursorLocation () { return coordinateSystemMapper.getCursorLocation(); } +Rectangle fitRectangleBoundsIntoMonitor(RECT rect) { + Rectangle monitorBounds = coordinateSystemMapper.getContainingMonitorBoundsInPixels(getCursorLocation()); + int rectWidth = rect.right - rect.left; + int rectHeight = rect.bottom - rect.top; + if (rect.left < monitorBounds.x) { + rect.left = monitorBounds.x; + } + int monitorBoundsRightEnd = monitorBounds.x + monitorBounds.width; + if (rect.right > monitorBoundsRightEnd) { + if (rectWidth <= monitorBounds.width) { + rect.left = monitorBoundsRightEnd - rectWidth; + } else { + rect.left = monitorBounds.x; + } + rectWidth = monitorBoundsRightEnd - rect.left; + } + return new Rectangle(rect.left, rect.top, rectWidth, rectHeight); +} + Point getCursorLocationInPixels () { POINT pt = new POINT (); OS.GetCursorPos (pt); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MultiZoomCoordinateSystemMapper.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MultiZoomCoordinateSystemMapper.java index 0edae112b5f..175c6db7f13 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MultiZoomCoordinateSystemMapper.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MultiZoomCoordinateSystemMapper.java @@ -271,4 +271,12 @@ private int getApplicableMonitorZoom(Monitor monitor) { return DPIUtil.getZoomForAutoscaleProperty(monitor.zoom); } + @Override + public Rectangle getContainingMonitorBoundsInPixels(Point point) { + Monitor monitor = point instanceof MonitorAwarePoint monitorAwarePoint + ? monitorAwarePoint.getMonitor() + :getContainingMonitorForPoints(point.x, point.y); + return getMonitorClientAreaInPixels(monitor); + } + } \ No newline at end of file diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/SingleZoomCoordinateSystemMapper.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/SingleZoomCoordinateSystemMapper.java index 84ae1fb64f7..e9253592996 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/SingleZoomCoordinateSystemMapper.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/SingleZoomCoordinateSystemMapper.java @@ -105,4 +105,17 @@ public Point getCursorLocation() { public void setCursorLocation(int x, int y) { display.setCursorLocationInPixels(DPIUtil.autoScaleUp(x), DPIUtil.autoScaleUp(y)); } + + @Override + public Rectangle getContainingMonitorBoundsInPixels(Point point) { + int zoom = DPIUtil.getDeviceZoom(); + point = DPIUtil.scaleUp(point, zoom); + for (Monitor monitor : display.getMonitors()) { + Rectangle monitorBounds = DPIUtil.scaleUp(monitor.getBounds(), zoom); + if (monitorBounds.contains(point)) { + return monitorBounds; + } + } + return null; + } } \ No newline at end of file diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java index 849d89b63d9..57c02a7130a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java @@ -7158,56 +7158,65 @@ LRESULT wmNotifyToolTip (NMHDR hdr, long wParam, long lParam) { case OS.TTN_SHOW: { LRESULT result = super.wmNotify (hdr, wParam, lParam); if (result != null) return result; - if (hdr.code != OS.TTN_SHOW) tipRequested = true; - long code = callWindowProc (handle, OS.WM_NOTIFY, wParam, lParam); - if (hdr.code != OS.TTN_SHOW) tipRequested = false; if (toolTipText != null) break; - if (isCustomToolTip ()) { - LVHITTESTINFO pinfo = new LVHITTESTINFO (); - int pos = OS.GetMessagePos (); - POINT pt = new POINT(); - OS.POINTSTOPOINT (pt, pos); - OS.ScreenToClient (handle, pt); - pinfo.x = pt.x; - pinfo.y = pt.y; - /* - * Bug in Windows. When LVM_SUBITEMHITTEST is used to hittest - * a point that is above the table, instead of returning -1 to - * indicate that the hittest failed, a negative index is returned. - * The fix is to consider any value that is negative a failure. - */ - if (OS.SendMessage (handle, OS.LVM_SUBITEMHITTEST, 0, pinfo) >= 0) { - TableItem item = _getItem (pinfo.iItem); - long hDC = OS.GetDC (handle); - long oldFont = 0, newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); - if (newFont != 0) oldFont = OS.SelectObject (hDC, newFont); - long hFont = item.fontHandle (pinfo.iSubItem); - if (hFont != -1) hFont = OS.SelectObject (hDC, hFont); - Event event = sendMeasureItemEvent (item, pinfo.iItem, pinfo.iSubItem, hDC); - if (!isDisposed () && !item.isDisposed ()) { - RECT itemRect = new RECT (); - Rectangle boundsInPixels = DPIUtil.scaleUp(event.getBounds(), getZoom()); - OS.SetRect (itemRect, boundsInPixels.x, boundsInPixels.y, boundsInPixels.x + boundsInPixels.width, boundsInPixels.y + boundsInPixels.height); - if (hdr.code == OS.TTN_SHOW) { - RECT toolRect = toolTipRect (itemRect); - OS.MapWindowPoints (handle, 0, toolRect, 2); - long hwndToolTip = OS.SendMessage (handle, OS.LVM_GETTOOLTIPS, 0, 0); - int flags = OS.SWP_NOACTIVATE | OS.SWP_NOZORDER; - int width = toolRect.right - toolRect.left, height = toolRect.bottom - toolRect.top; - OS.SetWindowPos (hwndToolTip, 0, toolRect.left , toolRect.top, width, height, flags); - } else { - NMTTDISPINFO lpnmtdi = new NMTTDISPINFO (); - OS.MoveMemory (lpnmtdi, lParam, NMTTDISPINFO.sizeof); - if (lpnmtdi.lpszText != 0) { - OS.MoveMemory (lpnmtdi.lpszText, new char [1], 2); - OS.MoveMemory (lParam, lpnmtdi, NMTTDISPINFO.sizeof); - } - RECT cellRect = item.getBounds (pinfo.iItem, pinfo.iSubItem, true, true, true, true, hDC); - RECT clientRect = new RECT (); - OS.GetClientRect (handle, clientRect); - if (itemRect.right > cellRect.right || itemRect.right > clientRect.right) { - //TEMPORARY CODE - String string = " "; + if (isCustomToolTip () || hdr.code == OS.TTN_SHOW) { + result = positionTooltip(hdr, lParam); + } + return result; + } + } + return null; +} + +private LRESULT positionTooltip(NMHDR hdr, long lParam) { + LRESULT result = null; + LVHITTESTINFO pinfo = new LVHITTESTINFO (); + int pos = OS.GetMessagePos (); + POINT pt = new POINT(); + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (handle, pt); + pinfo.x = pt.x; + pinfo.y = pt.y; + /* + * Bug in Windows. When LVM_SUBITEMHITTEST is used to hittest + * a point that is above the table, instead of returning -1 to + * indicate that the hittest failed, a negative index is returned. + * The fix is to consider any value that is negative a failure. + */ + if (OS.SendMessage (handle, OS.LVM_SUBITEMHITTEST, 0, pinfo) >= 0) { + TableItem item = _getItem (pinfo.iItem); + long hDC = OS.GetDC (handle); + long oldFont = 0, newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); + if (newFont != 0) oldFont = OS.SelectObject (hDC, newFont); + long hFont = item.fontHandle (pinfo.iSubItem); + if (hFont != -1) hFont = OS.SelectObject (hDC, hFont); + Event event = sendMeasureItemEvent (item, pinfo.iItem, pinfo.iSubItem, hDC); + if (!isDisposed () && !item.isDisposed ()) { + RECT itemRect = new RECT (); + Rectangle boundsInPixels = DPIUtil.scaleUp(event.getBounds(), getZoom()); + OS.SetRect (itemRect, boundsInPixels.x, boundsInPixels.y, boundsInPixels.x + boundsInPixels.width, boundsInPixels.y + boundsInPixels.height); + if (hdr.code == OS.TTN_SHOW) { + RECT toolRect = isCustomToolTip() ? toolTipRect (itemRect) : itemRect; + OS.MapWindowPoints (handle, 0, toolRect, 2); + long hwndToolTip = OS.SendMessage (handle, OS.LVM_GETTOOLTIPS, 0, 0); + int flags = OS.SWP_NOACTIVATE | OS.SWP_NOZORDER; + Rectangle adjustedTooltipBounds = getDisplay().fitRectangleBoundsIntoMonitor(toolRect); + OS.SetWindowPos(hwndToolTip, 0, adjustedTooltipBounds.x, adjustedTooltipBounds.y, + adjustedTooltipBounds.width, adjustedTooltipBounds.height, flags); + result = LRESULT.ONE; + } else { + NMTTDISPINFO lpnmtdi = new NMTTDISPINFO (); + OS.MoveMemory (lpnmtdi, lParam, NMTTDISPINFO.sizeof); + if (lpnmtdi.lpszText != 0) { + OS.MoveMemory (lpnmtdi.lpszText, new char [1], 2); + OS.MoveMemory (lParam, lpnmtdi, NMTTDISPINFO.sizeof); + } + RECT cellRect = item.getBounds (pinfo.iItem, pinfo.iSubItem, true, true, true, true, hDC); + RECT clientRect = new RECT (); + OS.GetClientRect (handle, clientRect); + if (itemRect.right > cellRect.right || itemRect.right > clientRect.right) { + //TEMPORARY CODE + String string = " "; // String string = null; // if (pinfo.iSubItem == 0) { // string = item.text; @@ -7215,25 +7224,21 @@ LRESULT wmNotifyToolTip (NMHDR hdr, long wParam, long lParam) { // String [] strings = item.strings; // if (strings != null) string = strings [pinfo.iSubItem]; // } - if (string != null) { - Shell shell = getShell (); - char [] chars = new char [string.length () + 1]; - string.getChars (0, string.length (), chars, 0); - shell.setToolTipText (lpnmtdi, chars); - OS.MoveMemory (lParam, lpnmtdi, NMTTDISPINFO.sizeof); - } - } - } + if (string != null) { + Shell shell = getShell (); + char [] chars = new char [string.length () + 1]; + string.getChars (0, string.length (), chars, 0); + shell.setToolTipText (lpnmtdi, chars); + OS.MoveMemory (lParam, lpnmtdi, NMTTDISPINFO.sizeof); } - if (hFont != -1) hFont = OS.SelectObject (hDC, hFont); - if (newFont != 0) OS.SelectObject (hDC, oldFont); - OS.ReleaseDC (handle, hDC); } } - return new LRESULT (code); } + if (hFont != -1) hFont = OS.SelectObject (hDC, hFont); + if (newFont != 0) OS.SelectObject (hDC, oldFont); + OS.ReleaseDC (handle, hDC); } - return null; + return result; } LRESULT wmNotifyToolTip (NMTTCUSTOMDRAW nmcd, long lParam) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java index aba750bf919..93e86fcba29 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java @@ -8175,15 +8175,10 @@ private LRESULT positionTooltip(NMHDR hdr, long wParam, long lParam, boolean man // triggers additional display messages to SWT, creating an infinite loop // of positioning and re-scaling events. // Refer: https://github.com/eclipse-platform/eclipse.platform.swt/issues/557 - Point cursorLocation = display.getCursorLocation(); - Rectangle monitorBounds = cursorLocation instanceof MonitorAwarePoint monitorAwarePoint - ? getContainingMonitorBoundsInMultiZoomCoordinateSystem(monitorAwarePoint) - : getContainingMonitorBoundsInSingleZoomCoordinateSystem(cursorLocation); - if (monitorBounds != null) { - Rectangle adjustedTooltipBounds = fitTooltipBoundsIntoMonitor(toolRect, monitorBounds); - OS.SetWindowPos (hdr.hwndFrom, 0, adjustedTooltipBounds.x, adjustedTooltipBounds.y, adjustedTooltipBounds.width, adjustedTooltipBounds.height, flags); - result = LRESULT.ONE; - } + + Rectangle adjustedTooltipBounds = getDisplay().fitRectangleBoundsIntoMonitor(toolRect); + OS.SetWindowPos (hdr.hwndFrom, 0, adjustedTooltipBounds.x, adjustedTooltipBounds.y, adjustedTooltipBounds.width, adjustedTooltipBounds.height, flags); + result = LRESULT.ONE; } else if (!managedTooltip) { // If managedTooltip is false and the cursor is not over the valid part of the // target cell, Windows may still try to display the default tooltip. Since we @@ -8196,45 +8191,6 @@ private LRESULT positionTooltip(NMHDR hdr, long wParam, long lParam, boolean man return result; } -/** - * Adjust the tool tip to fit in a single monitor either by shifting its position or by adjusting it's width. - */ -private Rectangle fitTooltipBoundsIntoMonitor(RECT tooltipBounds, Rectangle monitorBounds) { - int tooltipWidth = tooltipBounds.right - tooltipBounds.left; - int tooltipHeight = tooltipBounds.bottom - tooltipBounds.top; - if (tooltipBounds.left < monitorBounds.x) { - tooltipBounds.left = monitorBounds.x; - } - int monitorBoundsRightEnd = monitorBounds.x + monitorBounds.width; - if (tooltipBounds.right > monitorBoundsRightEnd) { - if (tooltipWidth <= monitorBounds.width) { - tooltipBounds.left = monitorBoundsRightEnd - tooltipWidth; - } else { - tooltipBounds.left = monitorBounds.x; - } - tooltipWidth = monitorBoundsRightEnd - tooltipBounds.left; - } - return new Rectangle(tooltipBounds.left, tooltipBounds.top, tooltipWidth, tooltipHeight); -} - -private Rectangle getContainingMonitorBoundsInSingleZoomCoordinateSystem(Point point) { - int zoom = getZoom(); - point = DPIUtil.scaleUp(point, zoom); - for (Monitor monitor : display.getMonitors()) { - Rectangle monitorBounds = DPIUtil.scaleUp(monitor.getBounds(), zoom); - if (monitorBounds.contains(point)) { - return monitorBounds; - } - } - return null; -} - -private Rectangle getContainingMonitorBoundsInMultiZoomCoordinateSystem(MonitorAwarePoint point) { - Monitor monitor = point.getMonitor(); - return new Rectangle(monitor.x, monitor.y, DPIUtil.scaleUp(monitor.width, monitor.zoom), - DPIUtil.scaleUp(monitor.height, monitor.zoom)); -} - LRESULT wmNotifyToolTip (NMTTCUSTOMDRAW nmcd, long lParam) { switch (nmcd.dwDrawStage) { case OS.CDDS_PREPAINT: {