1313 *******************************************************************************/
1414package org .eclipse .ui .internal .console ;
1515
16+ import java .util .ArrayList ;
1617import java .util .HashMap ;
18+ import java .util .List ;
1719import java .util .Map ;
1820import java .util .concurrent .ConcurrentHashMap ;
1921
6264public class ConsoleZoomHandler extends AbstractHandler implements IExecutableExtension {
6365
6466 /**
65- * Key used to remember, on the console itself, the custom font created for
66- * zooming, so it can be reused/replaced and eventually disposed.
67+ * Remember the custom fonts created for each console, so they can be disposed.
6768 */
68- public static final String ZOOM_FONT_ATTRIBUTE = ConsoleZoomHandler . class . getName () + ".zoomFont" ; //$NON-NLS-1$
69+ private static final Map < TextConsole , List < Font >> fontsMap = new HashMap <>();
6970
7071 /**
7172 * Key used to remember, on the console itself, that a mismatching font change
@@ -151,7 +152,7 @@ public void consolesAdded(IConsole[] consoles) {
151152 textConsole .addPropertyChangeListener (FONT_ENFORCER );
152153 ZoomState state = sZoomByType .get (typeKey (textConsole ));
153154 if (state != null ) {
154- applyHeight (textConsole , state .height ());
155+ Display . getDefault (). asyncExec (() -> applyHeight (textConsole , state .height () ));
155156 }
156157 }
157158 }
@@ -162,7 +163,7 @@ public void consolesRemoved(IConsole[] consoles) {
162163 for (IConsole console : consoles ) {
163164 if (console instanceof TextConsole textConsole ) {
164165 textConsole .removePropertyChangeListener (FONT_ENFORCER );
165- disposeZoomFont ( textConsole );
166+ Display . getDefault (). asyncExec (() -> disposeZoomFonts ( textConsole ) );
166167 }
167168 }
168169 }
@@ -221,10 +222,6 @@ private static void onFontChanged(TextConsole textConsole) {
221222 return ;
222223 }
223224 }
224- // genuine external change: our own custom zoom font, if any, is no longer
225- // the console's active font, so it must be disposed now instead of being
226- // leaked until the console (e.g. a long-lived ProcessConsole) is removed
227- disposeZoomFont (textConsole );
228225 sZoomByType .put (type , new ZoomState (currentHeight .intValue (), 0 ));
229226 persistZoomStates ();
230227 }
@@ -317,11 +314,6 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
317314 * @param delta the font size delta to apply, in points
318315 */
319316 public static void applyZoom (IWorkbenchPart part , int delta ) {
320- if (Display .getCurrent () == null ) {
321- Display .getDefault ().asyncExec (() -> applyZoom (part , delta ));
322- return ;
323- }
324-
325317 if (!(part instanceof IConsoleView consoleView )) {
326318 return ;
327319 }
@@ -390,11 +382,6 @@ private static Integer getFontHeight(TextConsole textConsole) {
390382 * @param height the font height to apply, in points
391383 */
392384 private static void applyHeight (TextConsole textConsole , int height ) {
393- if (Display .getCurrent () == null ) {
394- Display .getDefault ().asyncExec (() -> applyHeight (textConsole , height ));
395- return ;
396- }
397-
398385 // make sure this console's font is (still) being watched, in case it was
399386 // registered before the zoom handler class got loaded, or the listener
400387 // was otherwise not yet attached
@@ -415,61 +402,46 @@ private static void applyHeight(TextConsole textConsole, int height) {
415402 fd .setHeight (height );
416403 }
417404
418- Object oldAttribute = textConsole .getAttribute (ZOOM_FONT_ATTRIBUTE );
419- Font oldZoomFont = oldAttribute instanceof Font f ? f : null ;
420-
421- Font newZoomFont = new Font (currentFont .getDevice (), fontData );
422- // remember/apply before disposing the old one, in case they are the same object
423- textConsole .setAttribute (ZOOM_FONT_ATTRIBUTE , newZoomFont );
424- textConsole .setFont (newZoomFont );
425-
426- if (oldZoomFont != null && !oldZoomFont .isDisposed ()) {
427- disposeLater (oldZoomFont );
428- }
429- }
430-
431- /**
432- * Disposes the custom zoom font remembered on the given console, if any.
433- * Dispatches to the UI thread if necessary.
434- *
435- * @param textConsole the console whose zoom font should be disposed
436- */
437- private static void disposeZoomFont (TextConsole textConsole ) {
438- Object attribute = textConsole .getAttribute (ZOOM_FONT_ATTRIBUTE );
439- if (!(attribute instanceof Font font ) || font .isDisposed ()) {
440- return ;
441- }
442- if (Display .getCurrent () == null ) {
443- // see applyHeight(...) above for why asyncExec (not syncExec) is used
444- Display .getDefault ().asyncExec (() -> disposeZoomFont (textConsole ));
445- return ;
446- }
447- disposeLater (font );
448- textConsole .setAttribute (ZOOM_FONT_ATTRIBUTE , null );
405+ fontsMap .compute (textConsole , (console , oldZoomFonts ) -> {
406+ Font newZoomFont = new Font (currentFont .getDevice (), fontData );
407+ textConsole .setFont (newZoomFont );
408+ List <Font > oldFonts = oldZoomFonts ;
409+ if (oldFonts == null ) {
410+ oldFonts = new ArrayList <>();
411+ }
412+ oldFonts .add (newZoomFont );
413+ return oldFonts ;
414+ });
449415 }
450416
451417 /**
452- * Disposes the given font on a later UI cycle rather than immediately.
418+ * Disposes all custom fonts after console is removed on a later UI cycle rather
419+ * than immediately.
453420 * <p>
454421 * A font that was just replaced on a console (e.g. via
455- * {@link TextConsole#setFont(Font)}) may still be referenced for a little
456- * while by the viewer's internal rendering caches (e.g.
422+ * {@link TextConsole#setFont(Font)}) may still be referenced for a little while
423+ * by the viewer's internal rendering caches (e.g.
457424 * {@code StyledText}/{@code TextLayout} keep per-line layouts that are only
458425 * refreshed on their next repaint). Disposing it synchronously can therefore
459426 * cause a later, asynchronously dispatched repaint to fail with an
460- * {@code IllegalArgumentException} ("Argument not valid") when it tries to
461- * use the now-disposed font. Deferring the actual disposal by one UI cycle
462- * gives any such pending repaint a chance to pick up the new font first.
427+ * {@code IllegalArgumentException} ("Argument not valid") when it tries to use
428+ * the now-disposed font. Deferring the actual disposal by one UI cycle gives
429+ * any such pending repaint a chance to pick up the new font first.
463430 * </p>
464431 *
465- * @param font the font to dispose; must be called on the UI thread
432+ * @param textConsole the console whose zoom fonts should be disposed
466433 */
467- private static void disposeLater (Font font ) {
468- Display .getCurrent ().asyncExec (() -> {
469- if (!font .isDisposed ()) {
470- font .dispose ();
471- }
472- });
434+ private static void disposeZoomFonts (TextConsole textConsole ) {
435+ List <Font > oldFonts = fontsMap .remove (textConsole );
436+ if (oldFonts != null && !oldFonts .isEmpty ()) {
437+ Display .getDefault ().timerExec (100 , () -> {
438+ for (Font font : oldFonts ) {
439+ if (font != null && !font .isDisposed ()) {
440+ font .dispose ();
441+ }
442+ }
443+ });
444+ }
473445 }
474446}
475447
0 commit comments