diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java index 89947ab92bc..2175d13b49a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java @@ -2285,7 +2285,7 @@ long windowProc (long hwnd, int msg, long wParam, long lParam) { if (display.taskBar != null) { for (TaskItem item : display.taskBar.items) { if (item != null && item.shell == this) { - item.recreate (); + item.onTaskbarButtonCreated(); break; } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java index e1c26b5a82d..86e09819e59 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java @@ -38,6 +38,7 @@ public class TaskItem extends Item { TaskBar parent; Shell shell; + boolean hasTaskbarButton = false; int progress, progressState = SWT.DEFAULT; Image overlayImage; String overlayText = ""; @@ -89,6 +90,28 @@ protected void checkSubclass () { @Override void destroyWidget () { + if (null != parent.mTaskbarList3) { // extra safety just in case + // MSDN for 'ITaskbarList3::SetOverlayIcon' says: + // Because a single overlay is applied to the taskbar button instead + // of to the individual window thumbnails, this is a per-group + // feature rather than per-window. Requests for overlay icons can + // be received from individual windows in a taskbar group, but they + // do not queue. The last overlay received is the overlay shown. + // If the last overlay received is removed, the overlay that it + // replaced is restored so long as it is still active. As an example, + // windows 1, 2, and 3 set, in order, overlays A, B, and C. + // Because overlay C was received last, it is shown on the taskbar button. + // Window 2 calls SetOverlayIcon with a NULL value to remove overlay B. + // Window 3 then does the same to remove overlay C. + // Because window 1's overlay A is still active, that overlay is then + // displayed on the taskbar button. + // This implies that Shell should pass NULL before closing. + // + // Issue #603: On Win11 the lack of this actually caused wrong overlay + // to show after closing Shell. + parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); + } + parent.destroyItem (this); releaseHandle (); } @@ -188,16 +211,27 @@ public int getProgressState () { return progressState; } -void recreate () { - if (showingText) { - if (overlayText.length () != 0) updateText (); +void updateImageAndText () { + if (showingText && (overlayText.length () != 0)) { + updateText (); + } else if (overlayImage != null) { + updateImage (); } else { - if (overlayImage != null) updateImage (); + parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); } +} + +void updateAll () { + updateImageAndText (); if (progress != 0) updateProgress (); if (progressState != SWT.DEFAULT) updateProgressState (); } +void onTaskbarButtonCreated () { + this.hasTaskbarButton = true; + updateAll (); +} + @Override void releaseHandle () { super.releaseHandle (); @@ -287,15 +321,14 @@ public void setOverlayImage (Image overlayImage) { if (overlayImage != null && overlayImage.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); if (shell == null) return; this.overlayImage = overlayImage; - if (overlayImage != null) { - updateImage (); - } else { - if (overlayText.length () != 0) { - updateText (); - } else { - parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); - } - } + this.showingText = (this.overlayImage == null); + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + + updateImageAndText (); } /** @@ -329,15 +362,14 @@ public void setOverlayText (String overlayText) { if (overlayText == null) error (SWT.ERROR_NULL_ARGUMENT); if (shell == null) return; this.overlayText = overlayText; - if (overlayText.length () != 0) { - updateText (); - } else { - if (overlayImage != null) { - updateImage (); - } else { - parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); - } - } + this.showingText = (this.overlayText.length() != 0); + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + + updateImageAndText (); } /** @@ -371,6 +403,12 @@ public void setProgress (int progress) { progress = Math.max (0, Math.min (progress, PROGRESS_MAX)); if (this.progress == progress) return; this.progress = progress; + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + updateProgress (); } @@ -414,6 +452,12 @@ public void setProgressState (int progressState) { if (shell == null) return; if (this.progressState == progressState) return; this.progressState = progressState; + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + updateProgressState (); } @@ -429,7 +473,6 @@ public void handleEvent (Event event) { } void updateImage () { - showingText = false; Image image2 = null; long hIcon = 0; switch (overlayImage.type) { @@ -463,8 +506,7 @@ void updateProgressState () { parent.mTaskbarList3.SetProgressState(shell.handle, tbpFlags); } -void updateText () { - showingText = true; +long renderTextIcon () { /* Create resources */ int width = 16, height = 16; long hdc = OS.GetDC (0); @@ -547,6 +589,11 @@ void updateText () { OS.DeleteObject (hBitmap); OS.DeleteObject (hMask); + return hIcon; +} + +void updateText () { + long hIcon = renderTextIcon(); parent.mTaskbarList3.SetOverlayIcon(shell.handle, hIcon, 0); OS.DestroyIcon (hIcon); } diff --git a/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java new file mode 100644 index 00000000000..af54efd05ce --- /dev/null +++ b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2023 Syntevo and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Syntevo - initial API and implementation + *******************************************************************************/ + +package org.eclipse.swt.tests.manual; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +public final class Issue0603_WrongTaskBarOverlays { + public static void main(String[] args) { + final Display display = new Display(); + + final Shell shell = new Shell(display); + shell.setLayout(new GridLayout(1, true)); + + final Label hint = new Label(shell, 0); + hint.setText( + "1. Use Win11; Win10 is not enough\n" + + "2. Start the program and pin its icon on TaskBar\n" + + "3. Press the button below to open a few Shells with overlays\n" + + "4. Issue #603: when one of them is closed, it doesn't restore previous overlay correctly\n" + + " Note that Windows is documented to restore the overlay that was set last, contrary to\n" + + " overlay of currently active Shell\n" + + "5. Issue #603: when all of them is closed, there should be no overlay\n" + + "6. The checkboxes below are to test that patch didn't break setting text/image overlays" + ); + + Button button = new Button(shell, SWT.PUSH); + button.setText("Test"); + button.addListener(SWT.Selection, e -> { + for (int iShell = 0; iShell < 3; iShell++) { + Shell shell2 = new Shell(display); + shell2.setText("Shell #" + iShell); + shell2.setSize(250, 100); + shell2.open(); + + final TaskItem taskItem2 = display.getSystemTaskBar().getItem(shell2); + taskItem2.setOverlayText("#" + iShell); + + // Windows Explorer seems to get confused when multiple overlays are set quickly + // It will then sometimes put them in wrong order. + // The workaround is to wait and process display events after each shell + { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + } + + while (display.readAndDispatch()) { + } + } + } + }); + + final TaskItem taskItem = display.getSystemTaskBar().getItem(shell); + + Button chkText = new Button(shell, SWT.CHECK); + chkText.setText("Set Text overlay for me"); + chkText.addListener(SWT.Selection, e -> { + if (chkText.getSelection()) { + taskItem.setOverlayText("Main"); + } else { + taskItem.setOverlayText(""); + } + }); + + Image image = new Image(display, 16, 16); + { + GC gc = new GC(image); + gc.setBackground(display.getSystemColor(SWT.COLOR_RED)); + gc.setForeground(display.getSystemColor(SWT.COLOR_RED)); + gc.fillRectangle(0, 0, 16, 16); + gc.dispose(); + } + + Button chkImage = new Button(shell, SWT.CHECK); + chkImage.setText("Set Image overlay for me"); + chkImage.addListener(SWT.Selection, e -> { + if (chkImage.getSelection()) { + taskItem.setOverlayImage(image); + } else { + taskItem.setOverlayImage(null); + } + }); + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + image.dispose(); + } +}