Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -463,13 +463,11 @@ public void testRemovingConsoleDisposesZoomFont(TestInfo testInfo) throws Except
ConsoleZoomHandler.applyZoom(consoleView, 1);
TestUtil.processUIEvents();

Object attribute = console.getAttribute(ConsoleZoomHandler.ZOOM_FONT_ATTRIBUTE);
assertThat(attribute).as("a custom zoom font should have been created").isInstanceOf(Font.class); //$NON-NLS-1$
Font zoomFont = (Font) attribute;
Font zoomFont = console.getFont();
assertFalse(zoomFont.isDisposed(), "the zoom font must not be disposed while its console is still open"); //$NON-NLS-1$

removeConsoles(console);
TestUtil.processUIEvents();
TestUtil.processUIEvents(200);

assertTrue(zoomFont.isDisposed(), "the zoom font must be disposed once its console is removed"); //$NON-NLS-1$
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
*******************************************************************************/
package org.eclipse.ui.internal.console;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand Down Expand Up @@ -62,10 +64,9 @@
public class ConsoleZoomHandler extends AbstractHandler implements IExecutableExtension {

/**
* Key used to remember, on the console itself, the custom font created for
* zooming, so it can be reused/replaced and eventually disposed.
* Remember the custom fonts created for each console, so they can be disposed.
*/
public static final String ZOOM_FONT_ATTRIBUTE = ConsoleZoomHandler.class.getName() + ".zoomFont"; //$NON-NLS-1$
private static final Map<TextConsole, List<Font>> fontsMap = new HashMap<>();

/**
* Key used to remember, on the console itself, that a mismatching font change
Expand Down Expand Up @@ -151,7 +152,7 @@ public void consolesAdded(IConsole[] consoles) {
textConsole.addPropertyChangeListener(FONT_ENFORCER);
ZoomState state = sZoomByType.get(typeKey(textConsole));
if (state != null) {
applyHeight(textConsole, state.height());
Display.getDefault().asyncExec(() -> applyHeight(textConsole, state.height()));
}
}
}
Expand All @@ -162,7 +163,7 @@ public void consolesRemoved(IConsole[] consoles) {
for (IConsole console : consoles) {
if (console instanceof TextConsole textConsole) {
textConsole.removePropertyChangeListener(FONT_ENFORCER);
disposeZoomFont(textConsole);
Display.getDefault().asyncExec(() -> disposeZoomFonts(textConsole));
}
}
}
Expand Down Expand Up @@ -221,10 +222,6 @@ private static void onFontChanged(TextConsole textConsole) {
return;
}
}
// genuine external change: our own custom zoom font, if any, is no longer
// the console's active font, so it must be disposed now instead of being
// leaked until the console (e.g. a long-lived ProcessConsole) is removed
disposeZoomFont(textConsole);
sZoomByType.put(type, new ZoomState(currentHeight.intValue(), 0));
persistZoomStates();
}
Expand Down Expand Up @@ -317,11 +314,6 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
* @param delta the font size delta to apply, in points
*/
public static void applyZoom(IWorkbenchPart part, int delta) {
if (Display.getCurrent() == null) {
Display.getDefault().asyncExec(() -> applyZoom(part, delta));
return;
}

if (!(part instanceof IConsoleView consoleView)) {
return;
}
Comment thread
iloveeclipse marked this conversation as resolved.
Expand Down Expand Up @@ -390,11 +382,6 @@ private static Integer getFontHeight(TextConsole textConsole) {
* @param height the font height to apply, in points
*/
private static void applyHeight(TextConsole textConsole, int height) {
if (Display.getCurrent() == null) {
Display.getDefault().asyncExec(() -> applyHeight(textConsole, height));
return;
}

// make sure this console's font is (still) being watched, in case it was
// registered before the zoom handler class got loaded, or the listener
// was otherwise not yet attached
Expand All @@ -415,61 +402,46 @@ private static void applyHeight(TextConsole textConsole, int height) {
fd.setHeight(height);
}

Object oldAttribute = textConsole.getAttribute(ZOOM_FONT_ATTRIBUTE);
Font oldZoomFont = oldAttribute instanceof Font f ? f : null;

Font newZoomFont = new Font(currentFont.getDevice(), fontData);
// remember/apply before disposing the old one, in case they are the same object
textConsole.setAttribute(ZOOM_FONT_ATTRIBUTE, newZoomFont);
textConsole.setFont(newZoomFont);

if (oldZoomFont != null && !oldZoomFont.isDisposed()) {
disposeLater(oldZoomFont);
}
}

/**
* Disposes the custom zoom font remembered on the given console, if any.
* Dispatches to the UI thread if necessary.
*
* @param textConsole the console whose zoom font should be disposed
*/
private static void disposeZoomFont(TextConsole textConsole) {
Object attribute = textConsole.getAttribute(ZOOM_FONT_ATTRIBUTE);
if (!(attribute instanceof Font font) || font.isDisposed()) {
return;
}
if (Display.getCurrent() == null) {
// see applyHeight(...) above for why asyncExec (not syncExec) is used
Display.getDefault().asyncExec(() -> disposeZoomFont(textConsole));
return;
}
disposeLater(font);
textConsole.setAttribute(ZOOM_FONT_ATTRIBUTE, null);
fontsMap.compute(textConsole, (console, oldZoomFonts) -> {
Font newZoomFont = new Font(currentFont.getDevice(), fontData);
textConsole.setFont(newZoomFont);
List<Font> oldFonts = oldZoomFonts;
if (oldFonts == null) {
oldFonts = new ArrayList<>();
}
oldFonts.add(newZoomFont);
return oldFonts;
});
}

/**
* Disposes the given font on a later UI cycle rather than immediately.
* Disposes all custom fonts after console is removed on a later UI cycle rather
* than immediately.
* <p>
* A font that was just replaced on a console (e.g. via
* {@link TextConsole#setFont(Font)}) may still be referenced for a little
* while by the viewer's internal rendering caches (e.g.
* {@link TextConsole#setFont(Font)}) may still be referenced for a little while
Comment thread
iloveeclipse marked this conversation as resolved.
* by the viewer's internal rendering caches (e.g.
* {@code StyledText}/{@code TextLayout} keep per-line layouts that are only
* refreshed on their next repaint). Disposing it synchronously can therefore
* cause a later, asynchronously dispatched repaint to fail with an
* {@code IllegalArgumentException} ("Argument not valid") when it tries to
* use the now-disposed font. Deferring the actual disposal by one UI cycle
* gives any such pending repaint a chance to pick up the new font first.
* {@code IllegalArgumentException} ("Argument not valid") when it tries to use
* the now-disposed font. Deferring the actual disposal by one UI cycle gives
* any such pending repaint a chance to pick up the new font first.
* </p>
*
* @param font the font to dispose; must be called on the UI thread
* @param textConsole the console whose zoom fonts should be disposed
*/
private static void disposeLater(Font font) {
Display.getCurrent().asyncExec(() -> {
if (!font.isDisposed()) {
font.dispose();
}
});
private static void disposeZoomFonts(TextConsole textConsole) {
List<Font> oldFonts = fontsMap.remove(textConsole);
if (oldFonts != null && !oldFonts.isEmpty()) {
Display.getDefault().timerExec(100, () -> {
for (Font font : oldFonts) {
if (font != null && !font.isDisposed()) {
font.dispose();
}
}
});
}
}
}

Loading