From 55c0bb35b856d9ee4c35bc73c29eb87d32d6f83d Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Mon, 20 Jul 2026 18:50:44 +0200 Subject: [PATCH 1/6] feat: Add image registries (#364) - Add CopilotImages, a facade for the ui plug-in's images - Add CopilotJobsImages, a facade for the jobs plug-in's images - Improve plug-in instantiation - Add unit tests to ensure the resources can be loaded - Add fragment test bundle com.microsoft.copilot.eclipse.ui.jobs.test Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../.classpath | 11 + .../.project | 34 ++ .../.settings/org.eclipse.jdt.core.prefs | 9 + .../.settings/org.eclipse.m2e.core.prefs | 4 + .../META-INF/MANIFEST.MF | 15 + .../build.properties | 6 + .../fragment.xml | 5 + .../pom.xml | 54 +++ .../ui/jobs/CopilotJobsImagesTest.java | 41 ++ .../copilot/eclipse/ui/jobs/CopilotJobs.java | 19 + .../eclipse/ui/jobs/CopilotJobsImages.java | 99 +++++ .../copilot/eclipse/ui/CopilotImagesTest.java | 42 ++ .../copilot/eclipse/ui/CopilotUiTests.java | 5 +- .../copilot/eclipse/ui/CopilotImages.java | 358 ++++++++++++++++++ .../copilot/eclipse/ui/CopilotUi.java | 18 +- pom.xml | 1 + 16 files changed, 717 insertions(+), 4 deletions(-) create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/.classpath create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/.project create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/build.properties create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml create mode 100644 com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java create mode 100644 com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java create mode 100644 com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java create mode 100644 com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath b/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath new file mode 100644 index 00000000..38f401ad --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.project b/com.microsoft.copilot.eclipse.ui.jobs.test/.project new file mode 100644 index 00000000..9c18f8af --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.project @@ -0,0 +1,34 @@ + + + com.microsoft.copilot.eclipse.ui.jobs.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..62ef3488 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF b/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000..54f8cf3e --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: com.microsoft.copilot.eclipse.ui.jobs.test +Bundle-SymbolicName: com.microsoft.copilot.eclipse.ui.jobs.test;singleton:=true +Bundle-Version: 0.20.0.qualifier +Bundle-Vendor: GitHub Copilot +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Fragment-Host: com.microsoft.copilot.eclipse.ui.jobs +Automatic-Module-Name: com.microsoft.copilot.eclipse.ui.jobs.test +Import-Package: org.objenesis;version="[3.4.0,4.0.0)" +Require-Bundle: junit-jupiter-api;bundle-version="5.10.1", + junit-jupiter-params;bundle-version="5.10.1", + org.mockito.mockito-core;bundle-version="5.14.2", + org.mockito.junit-jupiter;bundle-version="5.10.2", + com.microsoft.copilot.eclipse.ui.jobs;bundle-version="0.20.0" diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties b/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties new file mode 100644 index 00000000..07c97efe --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties @@ -0,0 +1,6 @@ +source.. = src +output.. = target/classes +bin.includes = META-INF/,\ + .,\ + fragment.xml + diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml b/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml new file mode 100644 index 00000000..0606478f --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml b/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml new file mode 100644 index 00000000..7515e98e --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + com.microsoft.copilot.eclipse + github-copilot-for-eclipse + ${copilot-plugin-version} + + com.microsoft.copilot.eclipse.ui.jobs.test + eclipse-test-plugin + ${base.name} :: UI Jobs Tests + + + true + + + + + skip-tests-during-ui-probe + + + probe.script + + + + + + org.eclipse.tycho + tycho-surefire-plugin + ${tycho-version} + + true + + + + + + + + + + + org.eclipse.tycho + tycho-surefire-plugin + ${tycho-version} + + true + false + + + + + \ No newline at end of file diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java b/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java new file mode 100644 index 00000000..1df825c9 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui.jobs; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Verifies that every public constant in {@link CopilotJobsImages} refers to a bundle resource + * that actually exists on the classpath. + * + *

New constants are discovered automatically via reflection; no separate list needs to be + * maintained. + */ +class CopilotJobsImagesTest { + + static Stream iconPaths() { + return Stream.of(CopilotJobsImages.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith("IMG_")) + .map(f -> { + try { + return Arguments.of(f.getName(), f.get(null)); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot read constant: " + f.getName(), e); + } + }); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("iconPaths") + void testIconFileExists(String fieldName, String path) { + assertNotNull(CopilotJobsImages.class.getResource("/" + path), + "Bundle resource not found for constant " + fieldName + ": " + path); + } +} diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java index ec782952..899c455e 100644 --- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java +++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java @@ -3,10 +3,12 @@ package com.microsoft.copilot.eclipse.ui.jobs; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; @@ -17,11 +19,27 @@ */ public class CopilotJobs extends AbstractUIPlugin { + private static volatile CopilotJobs COPILOT_JOBS_PLUGIN; + + /** Returns the shared plugin instance. */ + public static CopilotJobs getPlugin() { + Assert.isNotNull(COPILOT_JOBS_PLUGIN); + return COPILOT_JOBS_PLUGIN; + } + + @Override + protected void initializeImageRegistry(ImageRegistry registry) { + CopilotJobsImages.initialize(registry); + } + @Override public void start(BundleContext context) throws Exception { super.start(context); + COPILOT_JOBS_PLUGIN = this; + // Explicitly call method from core to ensure core is activated CopilotCore.getPlugin(); + Job initWaitJob = new Job("Waiting for Copilot initialization") { @Override protected IStatus run(IProgressMonitor monitor) { @@ -42,5 +60,6 @@ protected IStatus run(IProgressMonitor monitor) { @Override public void stop(BundleContext context) throws Exception { super.stop(context); + COPILOT_JOBS_PLUGIN = null; } } diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java new file mode 100644 index 00000000..0f46a682 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui.jobs; + +import java.net.URL; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +/** + * Centralized access to all static icons in the Copilot Jobs UI bundle. + * + *

Bundle images are owned by the plugin's {@link ImageRegistry} and disposed automatically when + * the plugin stops. Callers must not dispose images returned by {@link #getImage(String)}. + */ +public final class CopilotJobsImages { + + // Path prefixes + private static final String ICONS_ROOT = "icons/"; + private static final String ICONS_STATUS = ICONS_ROOT + "status/"; + + // Icons + public static final String IMG_REPO = ICONS_ROOT + "repo.png"; + public static final String IMG_INFORMATION = ICONS_ROOT + "information.png"; + public static final String IMG_STATUS_LOADING = ICONS_STATUS + "loading.png"; + public static final String IMG_STATUS_COMPLETE = ICONS_STATUS + "complete.png"; + + private CopilotJobsImages() { + // prevent instantiation + } + + /** + * Returns the plugin's image registry. + */ + static ImageRegistry getImageRegistry() { + return CopilotJobs.getPlugin().getImageRegistry(); + } + + /** + * Registers all static icon descriptors. Called once from + * {@link CopilotJobs#initializeImageRegistry(ImageRegistry)}. + */ + static void initialize(ImageRegistry registry) { + register(registry, IMG_REPO); + register(registry, IMG_INFORMATION); + register(registry, IMG_STATUS_LOADING); + register(registry, IMG_STATUS_COMPLETE); + } + + private static void register(ImageRegistry registry, String path) { + URL url = CopilotJobsImages.class.getResource("/" + path); + ImageDescriptor descriptor = url != null + ? ImageDescriptor.createFromURL(url) : ImageDescriptor.getMissingImageDescriptor(); + registry.put(path, descriptor); + } + + /** + * Returns the image for the given key. The returned image is owned by the plugin registry; + * callers must not dispose it. + * + * @param key one of the {@code IMG_*} constants defined in this class + */ + public static Image getImage(String key) { + return getImageRegistry().get(key); + } + + /** + * Returns the {@link ImageDescriptor} for the given key. Useful where a descriptor is required, + * e.g. for {@code Action.setImageDescriptor()}. + * + * @param key one of the {@code IMG_*} constants defined in this class + */ + public static ImageDescriptor getImageDescriptor(String key) { + return getImageRegistry().getDescriptor(key); + } + + /** + * Convenience access to Eclipse's workbench shared images. + * + * @param imageId a constant from {@link ISharedImages}, e.g. {@link ISharedImages#IMG_OBJS_WARN_TSK} + */ + public static Image getSharedImage(String imageId) { + return PlatformUI.getWorkbench().getSharedImages().getImage(imageId); + } + + /** + * Convenience access to Eclipse's workbench shared image descriptors. + * + * @param imageId a constant from {@link ISharedImages} + */ + public static ImageDescriptor getSharedImageDescriptor(String imageId) { + return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId); + } + +} diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java new file mode 100644 index 00000000..5fda6d5c --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Verifies that every public constant in {@link CopilotImages} refers to a bundle resource that + * actually exists on the classpath. + * + *

This test is independent of a running Display or plugin registry — it only needs the bundle's + * classpath. It guards against typos in icon paths and missing files caused by renames or deletions. + * New constants are discovered automatically via reflection; no separate list needs to be maintained. + */ +class CopilotImagesTest { + + static Stream iconPaths() { + return Stream.of(CopilotImages.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith("IMG_")) + .map(f -> { + try { + return Arguments.of(f.getName(), f.get(null)); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot read constant: " + f.getName(), e); + } + }); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("iconPaths") + void testIconFileExists(String fieldName, String path) { + assertNotNull(CopilotImages.class.getResource("/" + path), + "Bundle resource not found for constant " + fieldName + ": " + path); + } +} diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java index de9f1851..82f8891a 100644 --- a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java @@ -17,8 +17,9 @@ class CopilotUiTests { @Test void testCopilotCoreWakeUp() throws Exception { - CopilotUi ui = new CopilotUi(); - ui.start(null); + Bundle uiBundle = Platform.getBundle("com.microsoft.copilot.eclipse.ui"); + assertNotNull(uiBundle); + uiBundle.start(); Job.getJobManager().join(CopilotCore.INIT_JOB_FAMILY, null); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java new file mode 100644 index 00000000..100f9e18 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java @@ -0,0 +1,358 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui; + +import java.net.URL; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +import com.microsoft.copilot.eclipse.ui.utils.UiUtils; + +/** + * Centralized access to all static icons in the Copilot UI bundle and to Eclipse shared images. + * + *

Bundle images are owned by the plugin's {@link ImageRegistry} and disposed automatically when + * the plugin stops. Callers must not dispose images returned by {@link #getImage}. + */ +public final class CopilotImages { + + // Path prefixes + private static final String ICONS_ROOT = "icons/"; + private static final String ICONS_CHAT = ICONS_ROOT + "chat/"; + private static final String ICONS_DROPDOWN = ICONS_ROOT + "dropdown/"; + private static final String ICONS_JOBS = ICONS_ROOT + "jobs/"; + private static final String ICONS_MCP = ICONS_ROOT + "mcp/"; + private static final String ICONS_QUOTA = ICONS_ROOT + "quota/"; + private static final String ICONS_QUICKSTART = "intro/quickstart/"; + private static final String ICONS_SPINNER = ICONS_ROOT + "spinner/"; + + // ---- Root icons ---------------------------------------------------------- + public static final String IMG_BLANK = ICONS_ROOT + "blank.png"; + public static final String IMG_CANCEL_STATUS = ICONS_ROOT + "cancel_status.png"; + public static final String IMG_CLOSE = ICONS_ROOT + "close.png"; + public static final String IMG_COMPLETE_STATUS = ICONS_ROOT + "complete_status.png"; + public static final String IMG_EDIT_KEYBOARD_SHORTCUTS = ICONS_ROOT + "edit_keyboard_shortcuts.png"; + public static final String IMG_EDIT_PREFERENCES = ICONS_ROOT + "edit_preferences.png"; + public static final String IMG_FEEDBACK_FORUM = ICONS_ROOT + "feedback_forum.png"; + public static final String IMG_GITHUB_COPILOT = ICONS_ROOT + "github_copilot.png"; + public static final String IMG_GITHUB_COPILOT_ERROR = ICONS_ROOT + "github_copilot_error.png"; + public static final String IMG_GITHUB_COPILOT_NOT_AUTHORIZED = ICONS_ROOT + "github_copilot_not_authorized.png"; + public static final String IMG_GITHUB_COPILOT_NOT_SIGNED_IN = ICONS_ROOT + "github_copilot_not_signed_in.png"; + public static final String IMG_GITHUB_COPILOT_SIGNED_IN = ICONS_ROOT + "github_copilot_signed_in.png"; + public static final String IMG_INFORMATION = ICONS_ROOT + "information.png"; + public static final String IMG_SIGNIN = ICONS_ROOT + "signin.png"; + public static final String IMG_SIGNOUT = ICONS_ROOT + "signout.png"; + + // ---- Chat icons ---------------------------------------------------------- + public static final String IMG_CHAT_ATTACH_CONTEXT = ICONS_CHAT + "attach_context.png"; + public static final String IMG_CHAT_BACK_ARROW = ICONS_CHAT + "back_arrow.png"; + public static final String IMG_CHAT_BACK_ARROW_GREY = ICONS_CHAT + "back_arrow_grey.png"; + public static final String IMG_CHAT_BREAKPOINT_AUTO = ICONS_CHAT + "breakpoint_auto.png"; + public static final String IMG_CHAT_BREAKPOINT_AUTO_DISABLED = ICONS_CHAT + "breakpoint_auto_disabled.png"; + public static final String IMG_CHAT_HISTORY_EDIT = ICONS_CHAT + "chat_history_edit.png"; + public static final String IMG_CHAT_COPILOT_AVATAR = ICONS_CHAT + "chat_message_copilot_avatar.png"; + public static final String IMG_CHAT_USER_AVATAR = ICONS_CHAT + "chat_message_user_avatar.png"; + public static final String IMG_CHATVIEW_ICON_CHAT = ICONS_CHAT + "chatview_icon_chat.png"; + public static final String IMG_CHATVIEW_ICON_CODE = ICONS_CHAT + "chatview_icon_code.png"; + public static final String IMG_CHATVIEW_ICON_LOADING = ICONS_CHAT + "chatview_icon_loading.png"; + public static final String IMG_CHATVIEW_ICON_NOT_AUTHORIZED = ICONS_CHAT + "chatview_icon_not_authorized.png"; + public static final String IMG_CHATVIEW_ICON_WELCOME = ICONS_CHAT + "chatview_icon_welcome.png"; + public static final String IMG_CLEAR_TODO = ICONS_CHAT + "clear_todo.png"; + public static final String IMG_CLEAR_TODO_DISABLED = ICONS_CHAT + "clear_todo_disable.png"; + public static final String IMG_CHAT_DOWN_ARROW = ICONS_CHAT + "down_arrow.png"; + public static final String IMG_CHAT_ENTER = ICONS_CHAT + "enter.png"; + public static final String IMG_CHAT_EYE = ICONS_CHAT + "eye.png"; + public static final String IMG_CHAT_EYE_DARK = ICONS_CHAT + "eye_dark.png"; + public static final String IMG_CHAT_EYE_CLOSED = ICONS_CHAT + "eye_closed.png"; + public static final String IMG_CHAT_EYE_CLOSED_DARK = ICONS_CHAT + "eye_closed_dark.png"; + public static final String IMG_CHAT_GUTTER_ARROW = ICONS_CHAT + "gutter-arrow.png"; + public static final String IMG_CHAT_KEEP = ICONS_CHAT + "keep.png"; + public static final String IMG_CHAT_KEYBOARD_TAB = ICONS_CHAT + "keyboard-tab.png"; + public static final String IMG_CHAT_RED_NOTICE = ICONS_CHAT + "red_notice.png"; + public static final String IMG_CHAT_RIGHT_ARROW = ICONS_CHAT + "right_arrow.png"; + public static final String IMG_CHAT_SEND = ICONS_CHAT + "send.png"; + public static final String IMG_CHAT_SEND_DISABLED = ICONS_CHAT + "send_disabled.png"; + public static final String IMG_CHAT_SEND_TO_JOB = ICONS_CHAT + "send_to_job.png"; + public static final String IMG_CHAT_SEND_TO_JOB_DISABLED = ICONS_CHAT + "send_to_job_disabled.png"; + public static final String IMG_CHAT_TOOLS = ICONS_CHAT + "tools.png"; + public static final String IMG_CHAT_TOOLS_DETECTED = ICONS_CHAT + "tools_detected.png"; + public static final String IMG_CHAT_TOOLS_DISABLED = ICONS_CHAT + "tools_disabled.png"; + + // ---- Todo icons ---------------------------------------------------------- + public static final String IMG_TODOS_FINISH = ICONS_CHAT + "todos_finish.png"; + public static final String IMG_TODOS_FINISH_DARK = ICONS_CHAT + "todos_finish_dark.png"; + public static final String IMG_TODOS_RUNNING = ICONS_CHAT + "todos_running.png"; + public static final String IMG_TODOS_RUNNING_DARK = ICONS_CHAT + "todos_running_dark.png"; + public static final String IMG_TODOS_WAITING = ICONS_CHAT + "todos_waiting.png"; + public static final String IMG_TODOS_WAITING_DARK = ICONS_CHAT + "todos_waiting_dark.png"; + + // ---- Dropdown icons ------------------------------------------------------ + public static final String IMG_DROPDOWN_DOWN_ARROW = ICONS_DROPDOWN + "down_arrow.png"; + public static final String IMG_DROPDOWN_DOWN_ARROW_DARK = ICONS_DROPDOWN + "down_arrow_dark.png"; + public static final String IMG_DROPDOWN_COMPLETE_STATUS = ICONS_DROPDOWN + "dropdown_complete_status.png"; + public static final String IMG_DROPDOWN_COMPLETE_STATUS_DARK = ICONS_DROPDOWN + "dropdown_complete_status_dark.png"; + public static final String IMG_DROPDOWN_WARNING = ICONS_DROPDOWN + "dropdown_warning.png"; + public static final String IMG_DROPDOWN_WARNING_DARK = ICONS_DROPDOWN + "dropdown_warning_dark.png"; + + // ---- Jobs pull-request icons (used from UI bundle Java code) ------------- + public static final String IMG_JOBS_PULL_REQUEST_BLACK = ICONS_JOBS + "pull_request_black.png"; + public static final String IMG_JOBS_PULL_REQUEST_WHITE = ICONS_JOBS + "pull_request_white.png"; + + // ---- MCP icons ----------------------------------------------------------- + public static final String IMG_MCP_DEFAULT_ICON = ICONS_MCP + "mcp_default_icon.png"; + public static final String IMG_MCP_MARKETPLACE_ICON = ICONS_MCP + "mcp_marketplace_icon.png"; + public static final String IMG_MCP_REGISTRY = ICONS_MCP + "mcp_registry.png"; + public static final String IMG_MCP_REFRESH = ICONS_MCP + "refresh.png"; + public static final String IMG_MCP_HISTORY = ICONS_MCP + "history.png"; + public static final String IMG_MCP_HISTORY_DARK = ICONS_MCP + "history_dark.png"; + public static final String IMG_MCP_REPOSITORY = ICONS_MCP + "repository.png"; + public static final String IMG_MCP_REPOSITORY_DARK = ICONS_MCP + "repository_dark.png"; + public static final String IMG_MCP_UPDATE = ICONS_MCP + "update.png"; + public static final String IMG_MCP_UPDATE_DARK = ICONS_MCP + "update_dark.png"; + public static final String IMG_MCP_VERSIONS = ICONS_MCP + "versions.png"; + public static final String IMG_MCP_VERSIONS_DARK = ICONS_MCP + "versions_dark.png"; + + // ---- Quota icons --------------------------------------------------------- + public static final String IMG_QUOTA_UPGRADE = ICONS_QUOTA + "upgrade.png"; + public static final String IMG_QUOTA_USAGE_BLUE = ICONS_QUOTA + "usage_blue.png"; + public static final String IMG_QUOTA_USAGE_RED = ICONS_QUOTA + "usage_red.png"; + public static final String IMG_QUOTA_USAGE_YELLOW = ICONS_QUOTA + "usage_yellow.png"; + + // ---- Quick Start icons --------------------------------------------------- + public static final String IMG_QUICKSTART_AGENT = ICONS_QUICKSTART + "quick_start_agent.png"; + public static final String IMG_QUICKSTART_ASK = ICONS_QUICKSTART + "quick_start_ask.png"; + public static final String IMG_QUICKSTART_COMPLETION = ICONS_QUICKSTART + "quick_start_completion.png"; + public static final String IMG_QUICKSTART_CLOSE_LIGHT = ICONS_QUICKSTART + "close_light.png"; + public static final String IMG_QUICKSTART_CLOSE_DARK = ICONS_QUICKSTART + "close_dark.png"; + public static final String IMG_QUICKSTART_CLOSE_HOVER_LIGHT = ICONS_QUICKSTART + "close_hover_light.png"; + public static final String IMG_QUICKSTART_CLOSE_HOVER_DARK = ICONS_QUICKSTART + "close_hover_dark.png"; + + // ---- Spinner frames (1-based index, see getSpinnerFrame) ----------------- + public static final String IMG_SPINNER_1 = ICONS_SPINNER + "1.png"; + public static final String IMG_SPINNER_2 = ICONS_SPINNER + "2.png"; + public static final String IMG_SPINNER_3 = ICONS_SPINNER + "3.png"; + public static final String IMG_SPINNER_4 = ICONS_SPINNER + "4.png"; + public static final String IMG_SPINNER_5 = ICONS_SPINNER + "5.png"; + public static final String IMG_SPINNER_6 = ICONS_SPINNER + "6.png"; + public static final String IMG_SPINNER_7 = ICONS_SPINNER + "7.png"; + public static final String IMG_SPINNER_8 = ICONS_SPINNER + "8.png"; + + public static final String[] SPINNER_FRAMES = { + IMG_SPINNER_1, IMG_SPINNER_2, IMG_SPINNER_3, IMG_SPINNER_4, + IMG_SPINNER_5, IMG_SPINNER_6, IMG_SPINNER_7, IMG_SPINNER_8 + }; + + private CopilotImages() { + // prevent instantiation + } + + /** + * Returns the plugin's image registry. + */ + static ImageRegistry getImageRegistry() { + return CopilotUi.getPlugin().getImageRegistry(); + } + + /** + * Registers all static icon descriptors. Called once from + * {@link CopilotUi#initializeImageRegistry(ImageRegistry)}. + */ + static void initialize(ImageRegistry registry) { + register(registry, IMG_BLANK); + register(registry, IMG_CANCEL_STATUS); + register(registry, IMG_CLOSE); + register(registry, IMG_COMPLETE_STATUS); + register(registry, IMG_EDIT_KEYBOARD_SHORTCUTS); + register(registry, IMG_EDIT_PREFERENCES); + register(registry, IMG_FEEDBACK_FORUM); + register(registry, IMG_GITHUB_COPILOT); + register(registry, IMG_GITHUB_COPILOT_ERROR); + register(registry, IMG_GITHUB_COPILOT_NOT_AUTHORIZED); + register(registry, IMG_GITHUB_COPILOT_NOT_SIGNED_IN); + register(registry, IMG_GITHUB_COPILOT_SIGNED_IN); + register(registry, IMG_INFORMATION); + register(registry, IMG_SIGNIN); + register(registry, IMG_SIGNOUT); + register(registry, IMG_CHAT_ATTACH_CONTEXT); + register(registry, IMG_CHAT_BACK_ARROW); + register(registry, IMG_CHAT_BACK_ARROW_GREY); + register(registry, IMG_CHAT_BREAKPOINT_AUTO); + register(registry, IMG_CHAT_BREAKPOINT_AUTO_DISABLED); + register(registry, IMG_CHAT_HISTORY_EDIT); + register(registry, IMG_CHAT_COPILOT_AVATAR); + register(registry, IMG_CHAT_USER_AVATAR); + register(registry, IMG_CHATVIEW_ICON_CHAT); + register(registry, IMG_CHATVIEW_ICON_CODE); + register(registry, IMG_CHATVIEW_ICON_LOADING); + register(registry, IMG_CHATVIEW_ICON_NOT_AUTHORIZED); + register(registry, IMG_CHATVIEW_ICON_WELCOME); + register(registry, IMG_CLEAR_TODO); + register(registry, IMG_CLEAR_TODO_DISABLED); + register(registry, IMG_CHAT_DOWN_ARROW); + register(registry, IMG_CHAT_ENTER); + register(registry, IMG_CHAT_EYE); + register(registry, IMG_CHAT_EYE_DARK); + register(registry, IMG_CHAT_EYE_CLOSED); + register(registry, IMG_CHAT_EYE_CLOSED_DARK); + register(registry, IMG_CHAT_GUTTER_ARROW); + register(registry, IMG_CHAT_KEEP); + register(registry, IMG_CHAT_KEYBOARD_TAB); + register(registry, IMG_CHAT_RED_NOTICE); + register(registry, IMG_CHAT_RIGHT_ARROW); + register(registry, IMG_CHAT_SEND); + register(registry, IMG_CHAT_SEND_DISABLED); + register(registry, IMG_CHAT_SEND_TO_JOB); + register(registry, IMG_CHAT_SEND_TO_JOB_DISABLED); + register(registry, IMG_CHAT_TOOLS); + register(registry, IMG_CHAT_TOOLS_DETECTED); + register(registry, IMG_CHAT_TOOLS_DISABLED); + register(registry, IMG_TODOS_FINISH); + register(registry, IMG_TODOS_FINISH_DARK); + register(registry, IMG_TODOS_RUNNING); + register(registry, IMG_TODOS_RUNNING_DARK); + register(registry, IMG_TODOS_WAITING); + register(registry, IMG_TODOS_WAITING_DARK); + register(registry, IMG_DROPDOWN_DOWN_ARROW); + register(registry, IMG_DROPDOWN_DOWN_ARROW_DARK); + register(registry, IMG_DROPDOWN_COMPLETE_STATUS); + register(registry, IMG_DROPDOWN_COMPLETE_STATUS_DARK); + register(registry, IMG_DROPDOWN_WARNING); + register(registry, IMG_DROPDOWN_WARNING_DARK); + register(registry, IMG_JOBS_PULL_REQUEST_BLACK); + register(registry, IMG_JOBS_PULL_REQUEST_WHITE); + register(registry, IMG_MCP_DEFAULT_ICON); + register(registry, IMG_MCP_MARKETPLACE_ICON); + register(registry, IMG_MCP_REGISTRY); + register(registry, IMG_MCP_REFRESH); + register(registry, IMG_MCP_HISTORY); + register(registry, IMG_MCP_HISTORY_DARK); + register(registry, IMG_MCP_REPOSITORY); + register(registry, IMG_MCP_REPOSITORY_DARK); + register(registry, IMG_MCP_UPDATE); + register(registry, IMG_MCP_UPDATE_DARK); + register(registry, IMG_MCP_VERSIONS); + register(registry, IMG_MCP_VERSIONS_DARK); + register(registry, IMG_QUOTA_UPGRADE); + register(registry, IMG_QUOTA_USAGE_BLUE); + register(registry, IMG_QUOTA_USAGE_RED); + register(registry, IMG_QUOTA_USAGE_YELLOW); + register(registry, IMG_QUICKSTART_AGENT); + register(registry, IMG_QUICKSTART_ASK); + register(registry, IMG_QUICKSTART_COMPLETION); + register(registry, IMG_QUICKSTART_CLOSE_LIGHT); + register(registry, IMG_QUICKSTART_CLOSE_DARK); + register(registry, IMG_QUICKSTART_CLOSE_HOVER_LIGHT); + register(registry, IMG_QUICKSTART_CLOSE_HOVER_DARK); + register(registry, IMG_SPINNER_1); + register(registry, IMG_SPINNER_2); + register(registry, IMG_SPINNER_3); + register(registry, IMG_SPINNER_4); + register(registry, IMG_SPINNER_5); + register(registry, IMG_SPINNER_6); + register(registry, IMG_SPINNER_7); + register(registry, IMG_SPINNER_8); + } + + private static void register(ImageRegistry registry, String path) { + URL url = CopilotImages.class.getResource("/" + path); + ImageDescriptor descriptor = url != null + ? ImageDescriptor.createFromURL(url) : ImageDescriptor.getMissingImageDescriptor(); + registry.put(path, descriptor); + } + + /** + * Returns the image for the given key. The returned image is owned by the plugin registry; + * callers must not dispose it. + * + * @param key one of the {@code IMG_*} constants defined in this class + */ + public static Image getImage(String key) { + return getImageRegistry().get(key); + } + + /** + * Returns the {@link ImageDescriptor} for the given key. Useful where a descriptor is required, + * e.g. for {@code Action.setImageDescriptor()}. + * + * @param key one of the {@code IMG_*} constants defined in this class + */ + public static ImageDescriptor getImageDescriptor(String key) { + return getImageRegistry().getDescriptor(key); + } + + /** + * Returns the theme-correct image for icons that come in light/dark variants. + * Uses {@link UiUtils#isDarkTheme()} to pick the right key. + * + *

Does not track live theme changes; callers re-render on theme-change events as needed. + * + * @param lightKey key to use in light theme + * @param darkKey key to use in dark theme + */ + public static Image getThemedImage(String lightKey, String darkKey) { + return getImage(UiUtils.isDarkTheme() ? darkKey : lightKey); + } + + /** + * Returns the theme-correct descriptor for icons that come in light/dark variants. + * + * @param lightKey key to use in light theme + * @param darkKey key to use in dark theme + * @see #getThemedImage(String, String) + */ + public static ImageDescriptor getThemedImageDescriptor(String lightKey, String darkKey) { + return getImageDescriptor(UiUtils.isDarkTheme() ? darkKey : lightKey); + } + + private static String getSpinnerFrameImageKey(int frame) { + Assert.isLegal(frame >= 1 && frame <= SPINNER_FRAMES.length, + "Spinner frame must be in [1, " + SPINNER_FRAMES.length + "], got: " + frame); + return SPINNER_FRAMES[frame - 1]; + } + + /** + * Returns the image for the given 1-based spinner frame (1–8). + * + * @param frame 1-based index of the spinner frame + * @throws IllegalArgumentException if {@code frame} is outside [1, 8] + */ + public static Image getSpinnerFrame(int frame) { + return getImage(getSpinnerFrameImageKey(frame)); + } + + /** + * Returns the image descriptor for the given 1-based spinner frame (1–8). + * + * @param frame 1-based index of the spinner frame + * @return the image descriptor for the requested spinner frame + * @throws IllegalArgumentException if {@code frame} is outside [1, 8] + */ + public static ImageDescriptor getSpinnerFrameDescriptor(int frame) { + return getImageDescriptor(getSpinnerFrameImageKey(frame)); + } + + /** + * Convenience access to Eclipse's workbench shared images. + * + * @param imageId a constant from {@link ISharedImages}, e.g. {@link ISharedImages#IMG_OBJS_ERROR_TSK} + */ + public static Image getSharedImage(String imageId) { + return PlatformUI.getWorkbench().getSharedImages().getImage(imageId); + } + + /** + * Convenience access to Eclipse's workbench shared image descriptors. + * + * @param imageId a constant from {@link ISharedImages} + */ + public static ImageDescriptor getSharedImageDescriptor(String imageId) { + return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId); + } + +} diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java index 66c472b4..869d0974 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java @@ -7,6 +7,7 @@ import java.util.concurrent.ExecutionException; import org.eclipse.core.net.proxy.IProxyService; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; @@ -18,6 +19,7 @@ import org.eclipse.core.runtime.preferences.ConfigurationScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; @@ -42,7 +44,7 @@ */ public class CopilotUi extends AbstractUIPlugin { - private static CopilotUi COPILOT_UI_PLUGIN = null; + private static volatile CopilotUi COPILOT_UI_PLUGIN; private CopilotStatusManager copilotStatusManager; private EditorLifecycleListener editorLifecycleListener; @@ -58,15 +60,24 @@ public class CopilotUi extends AbstractUIPlugin { */ public CopilotUi() { super(); - COPILOT_UI_PLUGIN = this; } + /** Returns the shared plugin instance. */ public static CopilotUi getPlugin() { + Assert.isNotNull(COPILOT_UI_PLUGIN); return COPILOT_UI_PLUGIN; } + @Override + protected void initializeImageRegistry(ImageRegistry registry) { + CopilotImages.initialize(registry); + } + @Override public void start(BundleContext context) throws Exception { + super.start(context); + COPILOT_UI_PLUGIN = this; + // Explicitly call method from core to ensure core is activated CopilotCore.getPlugin(); @@ -226,6 +237,9 @@ public void stop(BundleContext context) throws Exception { if (this.chatServiceManager != null) { this.chatServiceManager.dispose(); } + + super.stop(context); + COPILOT_UI_PLUGIN = null; } public EditorsManager getEditorsManager() { diff --git a/pom.xml b/pom.xml index 2a9b33d6..ae073c4a 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ com.microsoft.copilot.eclipse.core.test com.microsoft.copilot.eclipse.ui.test + com.microsoft.copilot.eclipse.ui.jobs.test com.microsoft.copilot.eclipse.swtbot.test From 08b05b83bed706e998b044453dab9ee7c0caf1ca Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 21 Jul 2026 11:25:33 +0200 Subject: [PATCH 2/6] Migrate image loading and disposal to using image registries (#364) Use image registries to access static images, no longer dispose them, since the registries handle Image disposals. Keep dynamic image loading and disposal as it was. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../eclipse/ui/jobs/views/JobsView.java | 34 ++-------- .../copilot/eclipse/ui/chat/ActionBar.java | 60 ++++++----------- .../eclipse/ui/chat/AddContextButton.java | 8 +-- .../eclipse/ui/chat/AgentMessageWidget.java | 10 +-- .../eclipse/ui/chat/AgentStatusLabel.java | 12 +--- .../eclipse/ui/chat/AgentToolCancelLabel.java | 10 +-- .../ui/chat/CurrentReferencedFile.java | 10 +-- .../ui/chat/ModelPickerGroupsBuilder.java | 23 +------ .../eclipse/ui/chat/ReferencedFile.java | 9 +-- .../eclipse/ui/chat/ThinkingBlock.java | 28 ++------ .../copilot/eclipse/ui/chat/TodoListBar.java | 58 ++++------------ .../eclipse/ui/chat/WorkingSetBar.java | 21 ++---- .../ui/chat/dialogs/DynamicOauthDialog.java | 8 +-- .../ui/chat/services/AvatarService.java | 10 +-- .../chat/tools/RunInTerminalToolAdapter.java | 4 +- .../chat/viewers/AfterLoginWelcomeViewer.java | 16 +---- .../ui/chat/viewers/AgentModeViewer.java | 23 ++----- .../viewers/BeforeLoginWelcomeViewer.java | 38 ++--------- .../ui/chat/viewers/ChatHistoryViewer.java | 20 ++---- .../ui/chat/viewers/LoadingViewer.java | 7 +- .../ui/chat/viewers/NoSubscriptionViewer.java | 7 +- .../MissingTerminalDependenciesDialog.java | 9 +-- .../ui/dialogs/jobs/BaseCopilotDialog.java | 8 +-- .../ui/dialogs/mcp/McpApprovalDialog.java | 9 +-- .../ui/dialogs/mcp/McpRegistryDialog.java | 29 ++------ .../ui/dialogs/mcp/McpServerDetailDialog.java | 38 ++++------- .../eclipse/ui/dialogs/mcp/McpServerItem.java | 25 +++---- .../ui/handlers/ShowContextMenuHandler.java | 6 +- .../ui/handlers/ShowMenuBarMenuHandler.java | 22 +++---- .../ui/handlers/ShowStatusBarMenuHandler.java | 47 +++++++------ .../ToolConfigurationContextMenuHandler.java | 5 +- .../copilot/eclipse/ui/nes/ActionMenu.java | 6 +- .../copilot/eclipse/ui/nes/RulerColumn.java | 3 +- .../ui/preferences/AddApiKeyDialog.java | 16 ++--- .../ui/preferences/AddByokModelDialog.java | 16 ++--- .../ui/preferences/ByokPreferencePage.java | 11 +--- .../ui/preferences/McpPreferencePage.java | 16 +---- .../eclipse/ui/quickstart/FeaturePage.java | 66 +++++-------------- .../eclipse/ui/swt/DropdownButton.java | 27 ++------ .../copilot/eclipse/ui/swt/DropdownPopup.java | 23 ++----- .../ui/swt/ModelHoverContentProvider.java | 26 ++------ .../eclipse/ui/swt/SpinnerAnimator.java | 15 ++--- .../copilot/eclipse/ui/utils/McpUtils.java | 7 +- .../copilot/eclipse/ui/utils/MenuUtils.java | 9 +-- 44 files changed, 229 insertions(+), 626 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java index d1ceb35a..e5774d3a 100644 --- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java +++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java @@ -50,9 +50,9 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrResponse; import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; +import com.microsoft.copilot.eclipse.ui.jobs.CopilotJobsImages; import com.microsoft.copilot.eclipse.ui.jobs.events.JobsViewEvents; import com.microsoft.copilot.eclipse.ui.jobs.i18n.Messages; -import com.microsoft.copilot.eclipse.ui.jobs.utils.UiUtils; /** * The view to display coding agent jobs with PRs from GitHub. Shows a 2-level @@ -83,8 +83,8 @@ public class JobsView { public void createPartControl(Composite parent) { parent.setLayout(new GridLayout(1, false)); - this.directoryIcon = UiUtils.buildImageFromPngPath("/icons/repo.png"); - this.informationIcon = UiUtils.buildImageFromPngPath("/icons/information.png"); + this.directoryIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_REPO); + this.informationIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_INFORMATION); treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); treeViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH)); @@ -108,19 +108,6 @@ public void createPartControl(Composite parent) { } }); - // Add dispose listener to clean up images - treeViewer.getTree().addDisposeListener(e -> { - if (directoryIcon != null && !directoryIcon.isDisposed()) { - directoryIcon.dispose(); - } - if (informationIcon != null && !informationIcon.isDisposed()) { - informationIcon.dispose(); - } - if (treeViewer.getLabelProvider() instanceof PullRequestLabelProvider labelProvider) { - labelProvider.disposeImages(); - } - }); - treeViewer.getTree().addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { @@ -572,7 +559,7 @@ public Image getImage(Object element) { private Image getStatusIconForLabel(String nodeText) { if (Messages.jobsView_label_loadingAgentJobs.equals(nodeText)) { if (this.loadingIcon == null) { - loadingIcon = UiUtils.buildImageFromPngPath("/icons/status/loading.png"); + loadingIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_LOADING); } return this.loadingIcon; } else if (SPECIAL_STATUS_LABELS.contains(nodeText)) { @@ -593,7 +580,7 @@ private Image getStatusIconForPr(GitHubPullRequestItem pr) { switch (pr.copilotWorkStatus()) { case done: if (this.completeIcon == null) { - completeIcon = UiUtils.buildImageFromPngPath("/icons/status/complete.png"); + completeIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_COMPLETE); } return this.completeIcon; case error: @@ -601,20 +588,11 @@ private Image getStatusIconForPr(GitHubPullRequestItem pr) { case in_progress: default: if (this.loadingIcon == null) { - loadingIcon = UiUtils.buildImageFromPngPath("/icons/status/loading.png"); + loadingIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_LOADING); } return this.loadingIcon; } } - public void disposeImages() { - if (loadingIcon != null && !loadingIcon.isDisposed()) { - loadingIcon.dispose(); - } - if (completeIcon != null && !completeIcon.isDisposed()) { - completeIcon.dispose(); - } - } } - } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java index 2c98738f..21654ec0 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java @@ -70,6 +70,7 @@ import com.microsoft.copilot.eclipse.core.utils.FileUtils; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.chat.contextwindow.ContextSizeDonut; @@ -336,21 +337,6 @@ private void updateTableLayout(Table table) { // Update send to job button and send button together updateButtonsLayout(); - - this.addDisposeListener(e -> { - if (mcpToolImage != null && !mcpToolImage.isDisposed()) { - mcpToolImage.dispose(); - } - if (mcpToolDisabledImage != null && !mcpToolDisabledImage.isDisposed()) { - mcpToolDisabledImage.dispose(); - } - if (mcpToolDetectedImage != null && !mcpToolDetectedImage.isDisposed()) { - mcpToolDetectedImage.dispose(); - } - if (redNoticeImage != null && !redNoticeImage.isDisposed()) { - redNoticeImage.dispose(); - } - }); } /** @@ -437,11 +423,11 @@ public void updateButtonsLayout() { // Add sendToJob button - only visible if clientPreviewFeaturesEnabled if (clientPreviewFeaturesEnabled) { - if (sendToJobImage == null || sendToJobImage.isDisposed()) { - sendToJobImage = UiUtils.buildImageFromPngPath("/icons/chat/send_to_job.png"); + if (sendToJobImage == null) { + sendToJobImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_TO_JOB); } - if (sendToJobDisabledImage == null || sendToJobDisabledImage.isDisposed()) { - sendToJobDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/send_to_job_disabled.png"); + if (sendToJobDisabledImage == null) { + sendToJobDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_TO_JOB_DISABLED); } this.sendToJobButton = UiUtils.createIconButton(this.bottomRightButtonsComposite, SWT.PUSH | SWT.FLAT); @@ -465,8 +451,8 @@ public void widgetSelected(SelectionEvent e) { // Add toggle button for all modes if it has not been created if (btnMsgToggle == null || btnMsgToggle.isDisposed()) { - this.sendImage = UiUtils.buildImageFromPngPath("/icons/chat/send.png"); - this.sendDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/send_disabled.png"); + this.sendImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND); + this.sendDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_DISABLED); this.btnMsgToggle = UiUtils.createIconButton(bottomRightButtonsComposite, SWT.PUSH | SWT.FLAT); boolean isEnabled = !StringUtils.isBlank(this.inputTextViewer.getContent()); this.btnMsgToggle.setEnabled(isEnabled); @@ -486,14 +472,6 @@ public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) { } } }); - this.btnMsgToggle.addDisposeListener(e -> { - if (sendImage != null && !sendImage.isDisposed()) { - sendImage.dispose(); - } - if (sendDisabledImage != null && !sendDisabledImage.isDisposed()) { - sendDisabledImage.dispose(); - } - }); AccessibilityUtils.addAccessibilityNameForUiComponent(this.btnMsgToggle, Messages.chat_actionBar_sendButton_Tooltip); } @@ -537,14 +515,14 @@ private void setUpChatModePicker(Composite parent) { } private void setUpMcpToolButtonInControlBar(Composite parent) { - if (mcpToolImage == null || mcpToolImage.isDisposed()) { - mcpToolImage = UiUtils.buildImageFromPngPath("/icons/chat/tools.png"); + if (mcpToolImage == null) { + mcpToolImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS); } - if (mcpToolDisabledImage == null || mcpToolDisabledImage.isDisposed()) { - mcpToolDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/tools_disabled.png"); + if (mcpToolDisabledImage == null) { + mcpToolDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS_DISABLED); } - if (mcpToolDetectedImage == null || mcpToolDetectedImage.isDisposed()) { - mcpToolDetectedImage = UiUtils.buildImageFromPngPath("/icons/chat/tools_detected.png"); + if (mcpToolDetectedImage == null) { + mcpToolDetectedImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS_DETECTED); } this.mcpToolButton = UiUtils.createIconButton(parent, SWT.PUSH | SWT.FLAT); @@ -584,11 +562,11 @@ private void setUpContextSizeDonutInControlBar(Composite parent) { } private void setUpAutoBreakpointButtonInControlBar(Composite parent) { - if (autoBreakpointImage == null || autoBreakpointImage.isDisposed()) { - autoBreakpointImage = UiUtils.buildImageFromPngPath("/icons/chat/breakpoint_auto.png"); + if (autoBreakpointImage == null) { + autoBreakpointImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_BREAKPOINT_AUTO); } - if (autoBreakpointDisabledImage == null || autoBreakpointDisabledImage.isDisposed()) { - autoBreakpointDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/breakpoint_auto_disabled.png"); + if (autoBreakpointDisabledImage == null) { + autoBreakpointDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_BREAKPOINT_AUTO_DISABLED); } this.autoBreakpointButton = UiUtils.createIconButton(parent, SWT.CHECK | SWT.FLAT); @@ -1090,8 +1068,8 @@ private void showMcpToolContextMenu() { // First menu item MenuItem approvalItem = new MenuItem(contextMenu, SWT.NONE); approvalItem.setText(Messages.chat_actionBar_toolButton_detected_toolTip); - if (redNoticeImage == null || redNoticeImage.isDisposed()) { - redNoticeImage = UiUtils.buildImageFromPngPath("/icons/chat/red_notice.png"); + if (redNoticeImage == null) { + redNoticeImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RED_NOTICE); } approvalItem.setImage(redNoticeImage); approvalItem.addSelectionListener(new SelectionAdapter() { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java index a9091824..3c023fd0 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java @@ -27,6 +27,7 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; import com.microsoft.copilot.eclipse.ui.i18n.Messages; @@ -50,7 +51,7 @@ public AddContextButton(Composite parent) { setLayout(layout); btnAttachIcon = UiUtils.createIconButton(this, SWT.PUSH | SWT.FLAT); - Image attachImage = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png"); + Image attachImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT); btnAttachIcon.setImage(attachImage); String attachTooltip = Messages.chat_addContext_tooltip; btnAttachIcon.setToolTipText(attachTooltip); @@ -61,11 +62,6 @@ public void widgetSelected(SelectionEvent e) { openFilePickerAndAddFiles(); } }); - btnAttachIcon.addDisposeListener(e -> { - if (attachImage != null && !attachImage.isDisposed()) { - attachImage.dispose(); - } - }); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java index fee673d9..10df6cac 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java @@ -17,6 +17,7 @@ import com.microsoft.copilot.eclipse.core.Constants; import com.microsoft.copilot.eclipse.core.lsp.protocol.codingagent.CodingAgentMessageRequestParams; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; @@ -55,15 +56,10 @@ private void buildMessageLabelWithIcon(CodingAgentMessageRequestParams params) { // Pull request icon Label iconLabel = new Label(titleComposite, SWT.TOP); - Image prImage = UiUtils.isDarkTheme() ? UiUtils.buildImageFromPngPath("/icons/jobs/pull_request_white.png") - : UiUtils.buildImageFromPngPath("/icons/jobs/pull_request_black.png"); + Image prImage = CopilotImages.getThemedImage(CopilotImages.IMG_JOBS_PULL_REQUEST_BLACK, + CopilotImages.IMG_JOBS_PULL_REQUEST_WHITE); iconLabel.setImage(prImage); iconLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); - iconLabel.addDisposeListener(e -> { - if (prImage != null && !prImage.isDisposed()) { - prImage.dispose(); - } - }); // Title label ChatMarkupViewer titleLabel = new ChatMarkupViewer(titleComposite, SWT.LEFT | SWT.WRAP); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java index 4979bfff..d61363b0 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java @@ -16,10 +16,10 @@ import org.osgi.service.event.EventHandler; import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; import com.microsoft.copilot.eclipse.ui.swt.SpinnerAnimator; import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A label with icon that displays the running status of the agent. @@ -49,12 +49,6 @@ public AgentStatusLabel(Composite parent, int style) { setLayout(layout); setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); this.addDisposeListener(e -> { - if (this.completedIcon != null && !this.completedIcon.isDisposed()) { - this.completedIcon.dispose(); - } - if (this.cancelledIcon != null && !this.cancelledIcon.isDisposed()) { - this.cancelledIcon.dispose(); - } if (this.eventBroker != null) { this.eventBroker.unsubscribe(cancelStatusHandler); } @@ -82,7 +76,7 @@ public void setCompletedStatus(String statusText) { spinner.stop(); if (this.completedIcon == null) { - this.completedIcon = UiUtils.buildImageFromPngPath("/icons/complete_status.png"); + this.completedIcon = CopilotImages.getImage(CopilotImages.IMG_COMPLETE_STATUS); } iconLabel.setImage(completedIcon); @@ -121,7 +115,7 @@ public void setCancelledStatus() { spinner.stop(); if (this.cancelledIcon == null) { - this.cancelledIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png"); + this.cancelledIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS); } iconLabel.setImage(cancelledIcon); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java index 4f7b1e5d..b8cff619 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java @@ -10,8 +10,8 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A label with icon that displays the cancel status of the agent tool. @@ -30,7 +30,7 @@ public AgentToolCancelLabel(Composite parent, int style, String cancelMessage) { setLayout(new GridLayout(2, false)); setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false)); - this.cancelIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png"); + this.cancelIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS); Label iconLabel = new Label(this, SWT.LEFT); iconLabel.setImage(this.cancelIcon); @@ -38,11 +38,5 @@ public AgentToolCancelLabel(Composite parent, int style, String cancelMessage) { textLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); textLabel.setText(cancelMessage); textLabel.setData(CssConstants.CSS_CLASS_NAME_KEY, "text-secondary"); - - this.addDisposeListener(e -> { - if (this.cancelIcon != null && !this.cancelIcon.isDisposed()) { - this.cancelIcon.dispose(); - } - }); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java index 19a9dde2..b9553c45 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java @@ -9,26 +9,22 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A special {@link ReferencedFile} that represents the current file being edited. This is used to represent the current * file in the chat view, when no file is selected. */ public class CurrentReferencedFile extends ReferencedFile { - private static Image visibleImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/eye.png").createImage(); - private static Image invisibleImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/eye_closed.png") - .createImage(); private Label descriptionLabel; private Label selectionLabel; @@ -81,9 +77,9 @@ public CurrentReferencedFile(Composite parent) { */ public void updateCloseClickBtnIcon(boolean isCurrentFileVisible) { if (isCurrentFileVisible) { - setCloseClickBtnIcon(visibleImage); + setCloseClickBtnIcon(CopilotImages.getImage(CopilotImages.IMG_CHAT_EYE)); } else { - setCloseClickBtnIcon(invisibleImage); + setCloseClickBtnIcon(CopilotImages.getImage(CopilotImages.IMG_CHAT_EYE_CLOSED)); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java index 46a245ee..31ca69a1 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java @@ -12,9 +12,9 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.preferences.ByokPreferencePage; import com.microsoft.copilot.eclipse.ui.swt.DropdownItem; @@ -30,8 +30,6 @@ */ public final class ModelPickerGroupsBuilder { - private static Image warningIcon; - private ModelPickerGroupsBuilder() { } @@ -140,23 +138,8 @@ private static Image resolveModelIcon(CopilotModel model) { if (StringUtils.isBlank(model.getDegradationReason())) { return null; } - if (warningIcon == null || warningIcon.isDisposed()) { - warningIcon = UiUtils.isDarkTheme() - ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_warning_dark.png") - : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_warning.png"); - Display display = SwtUtils.getDisplay(); - if (display != null) { - display.addListener(SWT.Dispose, event -> disposeWarningIcons()); - } - } - return warningIcon; - } - - private static void disposeWarningIcons() { - if (warningIcon != null && !warningIcon.isDisposed()) { - warningIcon.dispose(); - warningIcon = null; - } + return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_WARNING, + CopilotImages.IMG_DROPDOWN_WARNING_DARK); } private static void openManageModelsPreferences() { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java index e1bf9b25..bbfb0544 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java @@ -28,6 +28,7 @@ import org.eclipse.ui.model.WorkbenchLabelProvider; import com.microsoft.copilot.eclipse.core.Constants; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; import com.microsoft.copilot.eclipse.ui.i18n.Messages; @@ -81,15 +82,9 @@ public void mouseDown(MouseEvent e) { lblClose.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); setCloseClickAction(); - lblImage = UiUtils.buildImageFromPngPath("/icons/close.png"); + lblImage = CopilotImages.getImage(CopilotImages.IMG_CLOSE); setFile(file); - - this.addDisposeListener(e -> { - if (lblImage != null && !lblImage.isDisposed()) { - lblImage.dispose(); - } - }); this.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND)); // Add keyboard support for Enter activation diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingBlock.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingBlock.java index e273986f..78117200 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingBlock.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingBlock.java @@ -25,6 +25,7 @@ import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.CopilotCore; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; import com.microsoft.copilot.eclipse.ui.swt.SpinnerAnimator; @@ -87,8 +88,6 @@ public ThinkingBlock(Composite parent, int style) { createHeader(); createBody(); - addDisposeListener(e -> handleDispose()); - setTitle(Messages.thinking_title); spinner = new SpinnerAnimator(iconLabel); spinner.start(); @@ -135,7 +134,7 @@ public boolean showCancelled() { } stopSpinner(); if (cancelledIcon == null || cancelledIcon.isDisposed()) { - cancelledIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png"); + cancelledIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS); } if (!iconLabel.isDisposed()) { iconLabel.setImage(cancelledIcon); @@ -474,14 +473,14 @@ private void updateChevron() { Image image; String tooltip; if (expanded) { - if (downArrowImage == null || downArrowImage.isDisposed()) { - downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png"); + if (downArrowImage == null) { + downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW); } image = downArrowImage; tooltip = Messages.thinking_collapseTooltip; } else { - if (rightArrowImage == null || rightArrowImage.isDisposed()) { - rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png"); + if (rightArrowImage == null) { + rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW); } image = rightArrowImage; tooltip = Messages.thinking_expandTooltip; @@ -505,19 +504,4 @@ private void refreshEnclosingScroller() { } } - private void handleDispose() { - if (cancelledIcon != null && !cancelledIcon.isDisposed()) { - cancelledIcon.dispose(); - cancelledIcon = null; - } - if (downArrowImage != null && !downArrowImage.isDisposed()) { - downArrowImage.dispose(); - downArrowImage = null; - } - if (rightArrowImage != null && !rightArrowImage.isDisposed()) { - rightArrowImage.dispose(); - rightArrowImage = null; - } - } - } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java index abaae519..cf6bcd2c 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java @@ -23,6 +23,7 @@ import org.eclipse.swt.widgets.Label; import com.microsoft.copilot.eclipse.core.lsp.protocol.TodoItem; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService; import com.microsoft.copilot.eclipse.ui.chat.services.TodoListService; @@ -59,32 +60,15 @@ public TodoListBar(Composite parent, int style) { this.todoListService = CopilotUi.getPlugin().getChatServiceManager().getTodoListService(); this.chatFontService = CopilotUi.getPlugin().getChatServiceManager().getChatFontService(); loadStatusImages(); - this.addDisposeListener(e -> disposeStatusImages()); - } - - private void disposeStatusImages() { - if (completedImage != null && !completedImage.isDisposed()) { - completedImage.dispose(); - } - if (inProgressImage != null && !inProgressImage.isDisposed()) { - inProgressImage.dispose(); - } - if (notStartedImage != null && !notStartedImage.isDisposed()) { - notStartedImage.dispose(); - } } private void loadStatusImages() { - boolean isDarkTheme = UiUtils.isDarkTheme(); - if (isDarkTheme) { - completedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_finish_dark.png"); - inProgressImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_running_dark.png"); - notStartedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_waiting_dark.png"); - } else { - completedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_finish.png"); - inProgressImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_running.png"); - notStartedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_waiting.png"); - } + completedImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_FINISH, + CopilotImages.IMG_TODOS_FINISH_DARK); + inProgressImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_RUNNING, + CopilotImages.IMG_TODOS_RUNNING_DARK); + notStartedImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_WAITING, + CopilotImages.IMG_TODOS_WAITING_DARK); } Image getStatusImage(String status) { @@ -228,8 +212,6 @@ public void mouseUp(MouseEvent e) { addMouseListener(clickListener); updateDisplay(); - - addDisposeListener(e -> disposeImages()); } private void createExpandIcon() { @@ -280,8 +262,8 @@ public void widgetSelected(SelectionEvent e) { } private void loadClearButtonImages() { - clearEnabledImage = UiUtils.buildImageFromPngPath("/icons/chat/clear_todo.png"); - clearDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/clear_todo_disable.png"); + clearEnabledImage = CopilotImages.getImage(CopilotImages.IMG_CLEAR_TODO); + clearDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CLEAR_TODO_DISABLED); } public void updateDisplay() { @@ -322,13 +304,13 @@ private void updateExpandIcon() { if (isExpanded) { if (downArrowImage == null) { - downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png"); + downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW); } expandIcon.setImage(downArrowImage); setToolTipText(Messages.todoList_collapseTooltip); } else { if (rightArrowImage == null) { - rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png"); + rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW); } expandIcon.setImage(rightArrowImage); setToolTipText(Messages.todoList_expandTooltip); @@ -391,24 +373,6 @@ public void updateClearButtonState() { } } - private void disposeImages() { - if (downArrowImage != null && !downArrowImage.isDisposed()) { - downArrowImage.dispose(); - downArrowImage = null; - } - if (rightArrowImage != null && !rightArrowImage.isDisposed()) { - rightArrowImage.dispose(); - rightArrowImage = null; - } - if (clearEnabledImage != null && !clearEnabledImage.isDisposed()) { - clearEnabledImage.dispose(); - clearEnabledImage = null; - } - if (clearDisabledImage != null && !clearDisabledImage.isDisposed()) { - clearDisabledImage.dispose(); - clearDisabledImage = null; - } - } } class TodoListContent extends Composite { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java index 1c0780a6..d83903d2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java @@ -28,6 +28,7 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.model.WorkbenchLabelProvider; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService; import com.microsoft.copilot.eclipse.ui.chat.tools.ChangedFile; @@ -210,15 +211,6 @@ public void mouseUp(MouseEvent e) { addMouseListener(clickListener); updateTitleBarButtons(); - - this.addDisposeListener(e -> { - if (downArrowImage != null && !downArrowImage.isDisposed()) { - downArrowImage.dispose(); - } - if (rightArrowImage != null && !rightArrowImage.isDisposed()) { - rightArrowImage.dispose(); - } - }); } /** @@ -234,14 +226,14 @@ private void updateExpandCollapseState() { if (isExpanded) { // Expanded state: show down arrow and collapse tooltip if (downArrowImage == null) { - downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png"); + downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW); } expandIcon.setImage(downArrowImage); tooltipMessage = NLS.bind(Messages.fileChangeSummary_collapseTooltip, changeFilesTitle); } else { // Collapsed state: show right arrow and expand tooltip if (rightArrowImage == null) { - rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png"); + rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW); } expandIcon.setImage(rightArrowImage); tooltipMessage = NLS.bind(Messages.fileChangeSummary_expandTooltip, changeFilesTitle); @@ -488,12 +480,7 @@ public void mouseUp(MouseEvent e) { actionsArea.setVisible(false); keepButton = new Label(actionsArea, SWT.NONE); - Image keepImg = UiUtils.buildImageFromPngPath("/icons/chat/keep.png"); - this.addDisposeListener(e -> { - if (keepImg != null && !keepImg.isDisposed()) { - keepImg.dispose(); - } - }); + Image keepImg = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP); keepButton.setImage(keepImg); keepButton.setToolTipText(Messages.fileChangeSummary_keepButton); GridData keepGridData = new GridData(SWT.END, SWT.CENTER, false, false); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java index 77edec36..abfd23ca 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java @@ -24,7 +24,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.McpOauthRequest; import com.microsoft.copilot.eclipse.core.lsp.mcp.McpOauthRequest.DynamicOauthInput; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; /** * Dialog for handling Dynamic OAuth requests from MCP servers. @@ -57,7 +57,7 @@ protected void configureShell(Shell newShell) { newShell.setText(request.getTitle()); } - icon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png"); + icon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT); if (icon != null) { newShell.setImage(icon); } @@ -213,10 +213,6 @@ private void disposeResources() { headerFont.dispose(); headerFont = null; } - if (icon != null && !icon.isDisposed()) { - icon.dispose(); - icon = null; - } } @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java index 656a6f62..25edb66b 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java @@ -23,6 +23,7 @@ import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; @@ -31,9 +32,6 @@ */ public class AvatarService { private static final String AVATAR_URL = "https://avatars.githubusercontent.com/%s?s=24&v=4"; - private static final String DEFAULT_COPILOT_AVATAR_NAME = "/icons/chat/chat_message_copilot_avatar.png"; - private static final String DEFAULT_USER_AVATAR_NAME = "/icons/chat/chat_message_user_avatar.png"; - private Map avatarCache = new ConcurrentHashMap<>(); private Map jobs = new ConcurrentHashMap<>(); @@ -48,8 +46,8 @@ public class AvatarService { */ public AvatarService(AuthStatusManager authStatusManager) { this.authStatusManager = authStatusManager; - this.defaultGithubAvatar = UiUtils.buildImageFromPngPath(DEFAULT_COPILOT_AVATAR_NAME); - this.defaultUserAvatar = UiUtils.buildImageFromPngPath(DEFAULT_USER_AVATAR_NAME); + this.defaultGithubAvatar = CopilotImages.getImage(CopilotImages.IMG_CHAT_COPILOT_AVATAR); + this.defaultUserAvatar = CopilotImages.getImage(CopilotImages.IMG_CHAT_USER_AVATAR); this.authStatusChangedEventHandler = event -> { Object property = event.getProperty(IEventBroker.DATA); if (property instanceof CopilotStatusResult statusResult && statusResult.isSignedIn()) { @@ -137,8 +135,6 @@ protected IStatus run(IProgressMonitor monitor) { * Disposes the resources. */ public void dispose() { - defaultGithubAvatar.dispose(); - defaultUserAvatar.dispose(); avatarCache.values().forEach(Image::dispose); jobs.values().forEach(Job::cancel); if (this.eventBroker != null) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java index 2bfbdc11..67e1b1ca 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java @@ -25,12 +25,12 @@ import com.microsoft.copilot.eclipse.terminal.api.IRunInTerminalTool; import com.microsoft.copilot.eclipse.terminal.api.TerminalCommandProcessor; import com.microsoft.copilot.eclipse.terminal.api.TerminalServiceManager; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.ChatView; import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; import com.microsoft.copilot.eclipse.ui.utils.ResourceUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Adapter that bridges the UI tool interface with SPI-based terminal implementations. @@ -199,7 +199,7 @@ public CompletableFuture invoke(Map i isBackground = Boolean.parseBoolean((String) isBackgroundObj); } - impl.setTerminalIconDescriptor(UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png")); + impl.setTerminalIconDescriptor(CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT)); String workingDirectory = resolveWorkingDirectory(); return impl.executeCommand(command, isBackground, workingDirectory) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java index bb9ff033..e7d5241b 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java @@ -14,9 +14,9 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A widget that displays a initial chat introduction. @@ -82,13 +82,8 @@ private void buildMainIconAndLabel() { iconLabelComposite.setLayout(iconLabelGridlayout); iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png"); + Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME); this.copilotIconLabel = new Label(iconLabelComposite, SWT.NONE); - this.copilotIconLabel.addDisposeListener(e -> { - if (mainIcon != null && !mainIcon.isDisposed()) { - mainIcon.dispose(); - } - }); this.copilotIconLabel.setImage(mainIcon); this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); @@ -137,12 +132,7 @@ private void buildInstructions() { gridData.minimumHeight = 70; instructionComposite.setLayoutData(gridData); - Image attachContextIcon = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png"); - instructionComposite.addDisposeListener(e -> { - if (attachContextIcon != null && !attachContextIcon.isDisposed()) { - attachContextIcon.dispose(); - } - }); + Image attachContextIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT); buildLabelWithIcon(instructionComposite, attachContextIcon, Messages.chat_initialChatView_attachContextSuffix); WrapLabel subLabel = new WrapLabel(instructionComposite, SWT.CENTER); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java index 0d2d0bac..b58728f2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java @@ -14,9 +14,9 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A widget that displays a initial chat introduction. @@ -82,13 +82,8 @@ private void buildMainIconAndLabel() { iconLabelComposite.setLayout(iconLabelGridlayout); iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png"); + Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME); this.copilotIconLabel = new Label(iconLabelComposite, SWT.CENTER); - this.copilotIconLabel.addDisposeListener(e -> { - if (mainIcon != null && !mainIcon.isDisposed()) { - mainIcon.dispose(); - } - }); this.copilotIconLabel.setImage(mainIcon); this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); @@ -142,21 +137,11 @@ private void buildInstructions() { instructionComposite.setLayoutData(gridData); // configure MCP icon with label - Image configureMcpIcon = UiUtils.buildImageFromPngPath("/icons/chat/tools.png"); - instructionComposite.addDisposeListener(e -> { - if (configureMcpIcon != null && !configureMcpIcon.isDisposed()) { - configureMcpIcon.dispose(); - } - }); + Image configureMcpIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS); buildLabelWithIcon(instructionComposite, configureMcpIcon, Messages.chat_agentModeView_configureMcpSuffix); // attach context icon with label - Image attachContextIcon = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png"); - instructionComposite.addDisposeListener(e -> { - if (attachContextIcon != null && !attachContextIcon.isDisposed()) { - attachContextIcon.dispose(); - } - }); + Image attachContextIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT); buildLabelWithIcon(instructionComposite, attachContextIcon, Messages.chat_agentModeView_attachContextSuffix); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java index 26050db8..8f528ff2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java @@ -19,6 +19,7 @@ import org.eclipse.ui.handlers.IHandlerService; import com.microsoft.copilot.eclipse.core.CopilotCore; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; @@ -113,7 +114,7 @@ private void buildMainIconAndLabel(Composite parent) { iconLabelComposite.setLayout(iconLabelGridlayout); iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - this.mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png"); + this.mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME); this.copilotIconLabel = new Label(iconLabelComposite, SWT.CENTER); this.copilotIconLabel.setImage(mainIcon); this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, true)); @@ -139,28 +140,16 @@ private void buildSubComposite(Composite parent) { subComposite.setLayoutData(gridData); subComposite.setLayout(new GridLayout(1, true)); - if (this.agentIcon != null && !this.agentIcon.isDisposed()) { - this.agentIcon.dispose(); - } - this.agentIcon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png"); + this.agentIcon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT); buildLabelWithIcon(subComposite, agentIcon, Messages.chat_welcomeView_agentSuffix); - if (this.mcpIcon != null && !this.mcpIcon.isDisposed()) { - this.mcpIcon.dispose(); - } - this.mcpIcon = UiUtils.buildImageFromPngPath("/icons/chat/tools.png"); + this.mcpIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS); buildLabelWithIcon(subComposite, mcpIcon, Messages.chat_welcomeView_mcpSuffix); - if (this.chatIcon != null && !this.chatIcon.isDisposed()) { - this.chatIcon.dispose(); - } - this.chatIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_chat.png"); + this.chatIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CHAT); buildLabelWithIcon(subComposite, chatIcon, Messages.chat_welcomeView_chatSuffix); - if (this.codeIcon != null && !this.codeIcon.isDisposed()) { - this.codeIcon.dispose(); - } - this.codeIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_code.png"); + this.codeIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CODE); buildLabelWithIcon(subComposite, codeIcon, Messages.chat_welcomeView_completionSuffix); } @@ -242,24 +231,9 @@ private void buildFooterLinks() { @Override public void dispose() { - if (this.mainIcon != null) { - this.mainIcon.dispose(); - } if (this.mainLabelFont != null) { this.mainLabelFont.dispose(); } - if (this.codeIcon != null) { - this.codeIcon.dispose(); - } - if (this.chatIcon != null) { - this.chatIcon.dispose(); - } - if (this.agentIcon != null) { - this.agentIcon.dispose(); - } - if (this.mcpIcon != null) { - this.mcpIcon.dispose(); - } super.dispose(); } } \ No newline at end of file diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java index 09c8f0f4..bda3896b 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java @@ -36,6 +36,7 @@ import com.microsoft.copilot.eclipse.core.persistence.ConversationPersistenceManager; import com.microsoft.copilot.eclipse.core.persistence.ConversationXmlData; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.ConversationUtils; import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService; @@ -76,10 +77,10 @@ public ChatHistoryViewer(Composite parent, int style, List this.stylingEngine = PlatformUI.getWorkbench().getService(IStylingEngine.class); this.chatFontService = CopilotUi.getPlugin().getChatServiceManager().getChatFontService(); this.currentConversationId = currentConversationId; - this.backImage = UiUtils.buildImageFromPngPath( - UiUtils.isDarkTheme() ? "/icons/chat/back_arrow_grey.png" : "/icons/chat/back_arrow.png"); - this.enterImage = UiUtils.buildImageFromPngPath("/icons/chat/enter.png"); - this.editImage = UiUtils.buildImageFromPngPath("/icons/chat/chat_history_edit.png"); + this.backImage = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_BACK_ARROW, + CopilotImages.IMG_CHAT_BACK_ARROW_GREY); + this.enterImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_ENTER); + this.editImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_HISTORY_EDIT); // Assign CSS id for styling this.setData(CssConstants.CSS_ID_KEY, "chat-history-viewer"); @@ -91,17 +92,6 @@ public ChatHistoryViewer(Composite parent, int style, List setLayout(layout); createChatHistoryComposite(conversations, currentConversationId); - this.addDisposeListener(e -> { - if (backImage != null && !backImage.isDisposed()) { - backImage.dispose(); - } - if (enterImage != null && !enterImage.isDisposed()) { - enterImage.dispose(); - } - if (editImage != null && !editImage.isDisposed()) { - editImage.dispose(); - } - }); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java index b5a6ae25..cf75454f 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java @@ -13,9 +13,9 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A widget that displays a loading view. @@ -44,7 +44,7 @@ private void buildMainIconAndLabel() { iconLabelComposite.setLayout(iconLabelGridlayout); iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_loading.png"); + Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_LOADING); Label icon = new Label(iconLabelComposite, SWT.CENTER); icon.setImage(mainIcon); icon.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); @@ -62,9 +62,6 @@ private void buildMainIconAndLabel() { subLabel.setForeground(this.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); this.addDisposeListener(e -> { - if (mainIcon != null && !mainIcon.isDisposed()) { - mainIcon.dispose(); - } if (mainLabelFont != null && !mainLabelFont.isDisposed()) { mainLabelFont.dispose(); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java index 01b724d8..332fe7c2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java @@ -15,6 +15,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; @@ -23,7 +24,6 @@ * A widget that displays a no Copilot subscription info. */ public class NoSubscriptionViewer extends BaseViewer { - private Image mainIcon; private Font mainLabelFont; /** @@ -49,7 +49,7 @@ private void buildMainIconAndLabel() { iconLabelComposite.setLayout(iconLabelGridlayout); iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - this.mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_not_authorized.png"); + Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_NOT_AUTHORIZED); Label icon = new Label(iconLabelComposite, SWT.NONE); icon.setImage(mainIcon); icon.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); @@ -88,9 +88,6 @@ public void widgetSelected(org.eclipse.swt.events.SelectionEvent event) { @Override public void dispose() { - if (this.mainIcon != null) { - this.mainIcon.dispose(); - } if (this.mainLabelFont != null) { this.mainLabelFont.dispose(); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java index 7c237936..1e87cdfc 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java @@ -29,10 +29,10 @@ import com.microsoft.copilot.eclipse.core.Constants; import com.microsoft.copilot.eclipse.core.CopilotCore; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Dialog to inform users about missing terminal dependencies. Provides information about what needs to be installed and @@ -66,14 +66,9 @@ protected void configureShell(Shell shell) { } private void loadIcon(Shell shell) { - Image icon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png"); + Image icon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT); if (icon != null) { shell.setImage(icon); - shell.addDisposeListener(e -> { - if (!icon.isDisposed()) { - icon.dispose(); - } - }); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java index bff5569c..1f0cba10 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java @@ -17,6 +17,7 @@ import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -46,15 +47,10 @@ protected void configureShell(Shell newShell) { * @param shell the shell to set the icon on */ protected void loadCopilotIcon(Shell shell) { - Image dialogTitleImage = UiUtils.buildImageFromPngPath("/icons/github_copilot.png"); + Image dialogTitleImage = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT); if (dialogTitleImage != null) { shell.setImage(dialogTitleImage); } - shell.addDisposeListener(e -> { - if (dialogTitleImage != null && !dialogTitleImage.isDisposed()) { - dialogTitleImage.dispose(); - } - }); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java index 97a1a025..b55b886c 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java @@ -40,10 +40,10 @@ import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.chat.services.McpExtensionPointManager.McpRegistrationInfo; import com.microsoft.copilot.eclipse.ui.preferences.WrappableNoteLabel; import com.microsoft.copilot.eclipse.ui.utils.TextMateUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Dialog for approving third-party MCP providers. @@ -85,13 +85,8 @@ protected void configureShell(Shell newShell) { } private void loadImages(Shell shell) { - approvedImage = UiUtils.buildImageFromPngPath("/icons/chat/keep.png"); + approvedImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP); deniedImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE); - shell.addDisposeListener(e -> { - if (approvedImage != null && !approvedImage.isDisposed()) { - approvedImage.dispose(); - } - }); } @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java index d7d3124b..f315d215 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java @@ -39,6 +39,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerList; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.preferences.McpPreferencePage; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; @@ -91,15 +92,10 @@ protected void configureShell(Shell newShell) { newShell.setText(Messages.mcpRegistryDialog_mcpRegistry); newShell.setMinimumSize(MIN_WIDTH, SWT.DEFAULT); - Image dialogIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_registry.png"); + Image dialogIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REGISTRY); if (dialogIcon != null) { newShell.setImage(dialogIcon); } - newShell.addDisposeListener(e -> { - if (dialogIcon != null && !dialogIcon.isDisposed()) { - dialogIcon.dispose(); - } - }); } @Override @@ -443,22 +439,17 @@ private void createHeaderButtons(Composite parent) { private void createRefreshButton(Composite parent) { Button refreshButton = new Button(parent, SWT.PUSH); - Image refreshIcon = UiUtils.buildImageFromPngPath("/icons/mcp/refresh.png"); + Image refreshIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REFRESH); refreshButton.setImage(refreshIcon); refreshButton.setText(Messages.mcpRegistryDialog_button_refresh); refreshButton.setToolTipText(Messages.mcpRegistryDialog_button_refresh_tooltip); refreshButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); refreshButton.addListener(SWT.Selection, e -> refreshServerList()); - refreshButton.addDisposeListener(e -> { - if (refreshIcon != null && !refreshIcon.isDisposed()) { - refreshIcon.dispose(); - } - }); } private void createChangeUrlButton(Composite parent) { Button editButton = new Button(parent, SWT.PUSH); - Image editIcon = UiUtils.buildImageFromPngPath("/icons/edit_preferences.png"); + Image editIcon = CopilotImages.getImage(CopilotImages.IMG_EDIT_PREFERENCES); editButton.setImage(editIcon); editButton.setText(Messages.mcpRegistryDialog_button_changeUrl); editButton.setToolTipText(Messages.mcpRegistryDialog_button_changeUrl_tooltip); @@ -467,11 +458,6 @@ private void createChangeUrlButton(Composite parent) { e -> PreferencesUtil .createPreferenceDialogOn(getShell(), McpPreferencePage.ID, PreferencesUtils.getAllPreferenceIds(), null) .open()); - editButton.addDisposeListener(e -> { - if (editIcon != null && !editIcon.isDisposed()) { - editIcon.dispose(); - } - }); } /** @@ -531,14 +517,9 @@ private void createBanner(Composite parent) { // Create icon label on the right Label iconLabel = new Label(banner, SWT.NONE); - Image bannerIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_marketplace_icon.png"); + Image bannerIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_MARKETPLACE_ICON); if (bannerIcon != null) { iconLabel.setImage(bannerIcon); - iconLabel.addDisposeListener(e -> { - if (!bannerIcon.isDisposed()) { - bannerIcon.dispose(); - } - }); } iconLabel.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java index 6d36fef4..ffcd08f7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java @@ -48,6 +48,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Remote; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerDetail; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ButtonState; import com.microsoft.copilot.eclipse.ui.utils.McpUtils; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; @@ -179,15 +180,10 @@ protected void configureShell(Shell newShell) { : Messages.mcpServerDetailDialog_title; newShell.setText(title); - Image dialogIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_registry.png"); + Image dialogIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REGISTRY); if (dialogIcon != null) { newShell.setImage(dialogIcon); } - newShell.addDisposeListener(e -> { - if (dialogIcon != null && !dialogIcon.isDisposed()) { - dialogIcon.dispose(); - } - }); } @Override @@ -395,7 +391,8 @@ private void createDescriptionContent(Composite parent) { private void createVersionContent(Composite parent) { ServerDetail serverDetail = getServerDetail(); String version = serverDetail == null ? Messages.mcpServer_unknown : serverDetail.version(); - createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/versions_dark.png" : "/icons/mcp/versions.png", + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_VERSIONS, CopilotImages.IMG_MCP_VERSIONS_DARK); + createIconTextRow(parent, icon, Messages.mcpServerDetailDialog_version + " " + version, Messages.mcpServerDetailDialog_version + " " + version); } @@ -405,8 +402,8 @@ private void createPublishedContent(Composite parent) { String detailedDate = getDetailedFormattedDate(publishedAt); String text = relativeTime != null ? Messages.mcpServerDetailDialog_published + " " + relativeTime : Messages.mcpServerDetailDialog_noPublishedDate; - createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/history_dark.png" : "/icons/mcp/history.png", text, - detailedDate); + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_HISTORY, CopilotImages.IMG_MCP_HISTORY_DARK); + createIconTextRow(parent, icon, text, detailedDate); } private void createUpdatedContent(Composite parent) { @@ -415,13 +412,13 @@ private void createUpdatedContent(Composite parent) { String detailedDate = getDetailedFormattedDate(updatedAt); String text = relativeTime != null ? Messages.mcpServerDetailDialog_updated + " " + relativeTime : Messages.mcpServerDetailDialog_noUpdatedDate; - createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/update_dark.png" : "/icons/mcp/update.png", text, - detailedDate); + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_UPDATE, CopilotImages.IMG_MCP_UPDATE_DARK); + createIconTextRow(parent, icon, text, detailedDate); } private void createRepositoryLink(Composite parent) { - Composite row = createRowWithIcon(parent, - UiUtils.isDarkTheme() ? "/icons/mcp/repository_dark.png" : "/icons/mcp/repository.png"); + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_REPOSITORY, CopilotImages.IMG_MCP_REPOSITORY_DARK); + Composite row = createRowWithIcon(parent, icon); Link repoLink = new Link(row, SWT.NONE); repoLink.setText("" + Messages.mcpServerDetailDialog_repository + ""); @@ -438,11 +435,10 @@ private void createRepositoryLink(Composite parent) { } /** - * Builds a two-column row with an icon on the left and a text label on the right. Disposes the image when the icon - * label is disposed. + * Builds a two-column row with an icon on the left and a text label on the right. */ - private Composite createIconTextRow(Composite parent, String imagePath, String text, String tooltip) { - Composite row = createRowWithIcon(parent, imagePath); + private Composite createIconTextRow(Composite parent, Image icon, String text, String tooltip) { + Composite row = createRowWithIcon(parent, icon); Label textLabel = new Label(row, SWT.NONE); textLabel.setText(text != null ? text : ""); @@ -454,7 +450,7 @@ private Composite createIconTextRow(Composite parent, String imagePath, String t return row; } - private Composite createRowWithIcon(Composite parent, String imagePath) { + private Composite createRowWithIcon(Composite parent, Image icon) { GridLayout rowLayout = new GridLayout(2, false); rowLayout.marginWidth = 0; rowLayout.marginHeight = 0; @@ -462,14 +458,8 @@ private Composite createRowWithIcon(Composite parent, String imagePath) { row.setLayout(rowLayout); Label iconLabel = new Label(row, SWT.NONE); - final Image icon = UiUtils.buildImageFromPngPath(imagePath); iconLabel.setImage(icon); iconLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); - iconLabel.addDisposeListener(e -> { - if (icon != null && !icon.isDisposed()) { - icon.dispose(); - } - }); return row; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java index 2c15179e..923a09e9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java @@ -41,6 +41,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Remote; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerDetail; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ActionResult; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ActionType; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ButtonState; @@ -191,32 +192,30 @@ private void createMetaInfoRow(Composite parent, ServerDetail detail) { rowLayout.marginBottom = 0; metaRow.setLayout(rowLayout); - boolean isDark = UiUtils.isDarkTheme(); - // Version if (StringUtils.isNotBlank(version)) { - String iconPath = isDark ? "/icons/mcp/versions_dark.png" : "/icons/mcp/versions.png"; + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_VERSIONS, CopilotImages.IMG_MCP_VERSIONS_DARK); String text = Messages.mcpServerDetailDialog_version + " " + version; - createIconTextLabel(metaRow, iconPath, text, NLS.bind(Messages.mcpServerItem_versionTooltip, version)); + createIconTextLabel(metaRow, icon, text, NLS.bind(Messages.mcpServerItem_versionTooltip, version)); } // Published date if (StringUtils.isNotBlank(publishedAt)) { - String iconPath = isDark ? "/icons/mcp/history_dark.png" : "/icons/mcp/history.png"; + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_HISTORY, CopilotImages.IMG_MCP_HISTORY_DARK); String relativeTime = formatRelativeTime(publishedAt); String text = relativeTime != null ? Messages.mcpServerDetailDialog_published + " " + relativeTime : Messages.mcpServerDetailDialog_noPublishedDate; - createIconTextLabel(metaRow, iconPath, text, + createIconTextLabel(metaRow, icon, text, NLS.bind(Messages.mcpServerItem_publishedTooltip, formatDetailedDate(publishedAt))); } // Updated date if (StringUtils.isNotBlank(updatedAt)) { - String iconPath = isDark ? "/icons/mcp/update_dark.png" : "/icons/mcp/update.png"; + Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_UPDATE, CopilotImages.IMG_MCP_UPDATE_DARK); String relativeTime = formatRelativeTime(updatedAt); String text = relativeTime != null ? Messages.mcpServerDetailDialog_updated + " " + relativeTime : Messages.mcpServerDetailDialog_noUpdatedDate; - createIconTextLabel(metaRow, iconPath, text, + createIconTextLabel(metaRow, icon, text, NLS.bind(Messages.mcpServerItem_updatedTooltip, formatDetailedDate(updatedAt))); } } @@ -233,24 +232,16 @@ private OfficialMeta getMetaOfficial() { return serverResponse.meta().official(); } - private void createIconTextLabel(Composite parent, String iconPath, String text, String tooltip) { + private void createIconTextLabel(Composite parent, Image icon, String text, String tooltip) { Composite row = new Composite(parent, SWT.NONE); GridLayout rowLayout = new GridLayout(2, false); rowLayout.marginWidth = 0; rowLayout.marginHeight = 0; row.setLayout(rowLayout); - // Icon Label iconLabel = new Label(row, SWT.NONE); - Image icon = UiUtils.buildImageFromPngPath(iconPath); iconLabel.setImage(icon); - iconLabel.addDisposeListener(e -> { - if (icon != null && !icon.isDisposed()) { - icon.dispose(); - } - }); - // Text Label textLabel = new Label(row, SWT.NONE); textLabel.setText(text != null ? text : ""); if (tooltip != null) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java index 7ff74eec..1ebdb04d 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java @@ -18,9 +18,8 @@ import org.eclipse.ui.menus.CommandContributionItem; import org.eclipse.ui.menus.CommandContributionItemParameter; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.ResourceUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; - /** * Handler to show context menu items for adding files or folders to references in package explorer/project explorer. @@ -32,7 +31,6 @@ protected IContributionItem[] getContributionItems() { IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); var sel = win != null ? win.getSelectionService().getSelection() : null; - if (!(sel instanceof IStructuredSelection s) || s.isEmpty()) { return new IContributionItem[0]; } @@ -49,7 +47,7 @@ protected IContributionItem[] getContributionItems() { items.add(new Separator("com.microsoft.copilot.eclipse.ui.contextMenu.start")); - ImageDescriptor menuIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"); + ImageDescriptor menuIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT); MenuManager submenu = new MenuManager("Copilot", menuIcon, "com.microsoft.copilot.eclipse.ui.contextMenu"); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java index 5ea52002..829385dc 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java @@ -32,13 +32,13 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan; import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.preferences.LanguageServerSettingManager; import com.microsoft.copilot.eclipse.ui.utils.MenuUtils; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Handler for showing GitHub Copilot menu bar menu. @@ -87,7 +87,7 @@ protected IContributionItem[] getContributionItems() { // menu: username/Sign In if (CopilotStatusResult.NOT_SIGNED_IN.equals(status)) { items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.signIn", Messages.menu_signToGitHub, - UiUtils.buildImageDescriptorFromPngPath("/icons/signin.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNIN))); } else if (CopilotStatusResult.OK.equals(status)) { String userName = authStatusManager.getUserName(); String planLabel = MenuUtils.getPlanLabel(authStatusManager.getQuotaStatus().copilotPlan()); @@ -102,7 +102,7 @@ protected IContributionItem[] getContributionItems() { // menu: openChatView items.add(new Separator()); items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openChatView", Messages.menu_openChatView, - UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT))); // menu:(label options) Turn off Completions or Turn on Completions LanguageServerSettingManager languageServerSettingManager = CopilotUi.getPlugin().getLanguageServerSettingManager(); @@ -111,29 +111,29 @@ protected IContributionItem[] getContributionItems() { String label = languageServerSettingManager.isAutoShowCompletionEnabled() ? Messages.menu_turnOffCompletions : Messages.menu_turnOnCompletions; items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.autoShowCompletions", label, - UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK))); } // menu: editKeyboardShortcuts items.add(new Separator()); items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openEditKeyboardShortcuts", Messages.menu_editKeyboardShortcuts, - UiUtils.buildImageDescriptorFromPngPath("/icons/edit_keyboard_shortcuts.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_KEYBOARD_SHORTCUTS))); // menu: editPreferences items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openPreferences", Messages.menu_editPreferences, - UiUtils.buildImageDescriptorFromPngPath("/icons/edit_preferences.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_PREFERENCES))); // menu: giveFeedback items.add(new Separator()); Map parameters = Map.of(UiConstants.OPEN_URL_PARAMETER_NAME, UiConstants.COPILOT_FEEDBACK_FORUM_URL); items.add(createCommandItem(UiConstants.OPEN_URL_COMMAND_ID, Messages.menu_giveFeedback, parameters, - UiUtils.buildImageDescriptorFromPngPath("/icons/feedback_forum.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_FEEDBACK_FORUM))); // menu: whatIsNew items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.showWhatIsNew", Messages.menu_whatIsNew, - UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK))); // menu: Copilot settings and Sign Out addAuthenticationActions(items, status); @@ -222,7 +222,7 @@ private void addCopilotUsageItemsTbb(List items, CheckQuotaRe } // Upsell actions based on the user's plan - ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png"); + ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE); // For non-free users (excluding org-unlimited business/enterprise): // show "Enable Additional Usage" or "Increase Budget" depending on overage state. @@ -319,7 +319,7 @@ private void addCopilotUsageItemsLegacy(List items, CheckQuot } // Upsell actions based on the user's plan (legacy wording). - ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png"); + ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE); if (plan == CopilotPlan.free) { items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan", Messages.menu_quota_updateCopilotToPro, Messages.menu_quota_updateCopilotToProPlus, upgradeIcon)); @@ -339,7 +339,7 @@ private void addAuthenticationActions(List items, String stat Messages.menu_configureGitHubCopilotSettings, null)); } items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.signOut", Messages.menu_signOutOfGitHub, - UiUtils.buildImageDescriptorFromPngPath("/icons/signout.png"))); + CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNOUT))); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java index 178e23e3..e2db3c69 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java @@ -41,13 +41,13 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan; import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.preferences.LanguageServerSettingManager; import com.microsoft.copilot.eclipse.ui.utils.MenuUtils; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Handler for showing GitHub Copilot status bar menu. @@ -198,36 +198,33 @@ public void updateElement(UIElement element, Map parameters) { return; } else { String copilotStatus = authStatusManager.getCopilotStatus(); - String iconPath = null; + String iconKey = null; switch (copilotStatus) { case CopilotStatusResult.OK: - iconPath = "/icons/github_copilot_signed_in.png"; + iconKey = CopilotImages.IMG_GITHUB_COPILOT_SIGNED_IN; break; case CopilotStatusResult.LOADING: scheduleSpinnerJob(element); return; case CopilotStatusResult.ERROR, CopilotStatusResult.WARNING: - iconPath = "/icons/github_copilot_error.png"; + iconKey = CopilotImages.IMG_GITHUB_COPILOT_ERROR; break; case CopilotStatusResult.NOT_AUTHORIZED: - iconPath = "/icons/github_copilot_not_authorized.png"; + iconKey = CopilotImages.IMG_GITHUB_COPILOT_NOT_AUTHORIZED; break; case CopilotStatusResult.NOT_SIGNED_IN: default: - iconPath = "/icons/github_copilot_not_signed_in.png"; + iconKey = CopilotImages.IMG_GITHUB_COPILOT_NOT_SIGNED_IN; } - setIconOnDisplayThread(element, iconPath); + setIconOnDisplayThread(element, CopilotImages.getImageDescriptor(iconKey)); } } } - private void setIconOnDisplayThread(UIElement element, String iconPath) { - if (iconPath != null) { - SwtUtils.invokeOnDisplayThread(() -> { - ImageDescriptor newIcon = UiUtils.buildImageDescriptorFromPngPath(iconPath); - element.setIcon(newIcon); - }); + private void setIconOnDisplayThread(UIElement element, ImageDescriptor icon) { + if (icon != null) { + SwtUtils.invokeOnDisplayThread(() -> element.setIcon(icon)); } } @@ -236,7 +233,7 @@ private void addSignInOrUsernameAction(MenuManager menuManager) { if (CopilotStatusResult.NOT_SIGNED_IN.equals(status)) { MenuActionFactory.createMenuAction(menuManager, Messages.menu_signToGitHub, - UiUtils.buildImageDescriptorFromPngPath("/icons/signin.png"), handlerService, + CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNIN), handlerService, "com.microsoft.copilot.eclipse.commands.signIn", true); } else if (CopilotStatusResult.OK.equals(status)) { String userName = authStatusManager.getUserName(); @@ -326,7 +323,7 @@ private void addCopilotUsageActionTbb(MenuManager menuManager, CheckQuotaResult } // Upsell actions based on the user's plan - ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png"); + ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE); // For non-free users (excluding org-unlimited business/enterprise): // show "Enable Additional Usage" or "Increase Budget" depending on overage state. @@ -423,7 +420,7 @@ private void addCopilotUsageActionLegacy(MenuManager menuManager, CheckQuotaResu } // Upsell actions based on the user's plan (legacy wording). - ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png"); + ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE); if (plan == CopilotPlan.free) { MenuActionFactory.createMenuAction(menuManager, Messages.menu_quota_updateCopilotToPro, upgradeIcon, handlerService, "com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan", true); @@ -435,7 +432,7 @@ private void addCopilotUsageActionLegacy(MenuManager menuManager, CheckQuotaResu } private void addOpenChatViewAction(MenuManager menuManager) { - ImageDescriptor icon = UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"); + ImageDescriptor icon = CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT); MenuActionFactory.createMenuAction(menuManager, Messages.menu_openChatView, icon, handlerService, "com.microsoft.copilot.eclipse.commands.openChatView", true); } @@ -443,26 +440,26 @@ private void addOpenChatViewAction(MenuManager menuManager) { private void addLinkToFeedbackForumAction(MenuManager menuManager) { Map parameters = Map.of(UiConstants.OPEN_URL_PARAMETER_NAME, UiConstants.COPILOT_FEEDBACK_FORUM_URL); - ImageDescriptor feedbackIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/feedback_forum.png"); + ImageDescriptor feedbackIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_FEEDBACK_FORUM); MenuActionFactory.createMenuAction(menuManager, Messages.menu_giveFeedback, feedbackIcon, handlerService, UiConstants.OPEN_URL_COMMAND_ID, parameters, true); } private void addPreferencesAction(MenuManager menuManager) { - ImageDescriptor editPreferencesIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/edit_preferences.png"); + ImageDescriptor editPreferencesIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_PREFERENCES); MenuActionFactory.createMenuAction(menuManager, Messages.menu_editPreferences, editPreferencesIcon, handlerService, "com.microsoft.copilot.eclipse.commands.openPreferences", true); } private void addEditKeyboardShortcutsAction(MenuManager menuManager) { - ImageDescriptor editKeyboardShortcutsIcon = UiUtils - .buildImageDescriptorFromPngPath("/icons/edit_keyboard_shortcuts.png"); + ImageDescriptor editKeyboardShortcutsIcon = + CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_KEYBOARD_SHORTCUTS); MenuActionFactory.createMenuAction(menuManager, Messages.menu_editKeyboardShortcuts, editKeyboardShortcutsIcon, handlerService, "com.microsoft.copilot.eclipse.commands.openEditKeyboardShortcuts", true); } private void addCompletionSettingsAction(MenuManager menuManager) { - ImageDescriptor placeHolder = UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png"); + ImageDescriptor placeHolder = CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK); if (languageServerSettingManager.isAutoShowCompletionEnabled()) { MenuActionFactory.createMenuAction(menuManager, Messages.menu_turnOffCompletions, placeHolder, handlerService, "com.microsoft.copilot.eclipse.commands.autoShowCompletions", true); @@ -473,7 +470,7 @@ private void addCompletionSettingsAction(MenuManager menuManager) { } private void addShowWhatIsNewAction(MenuManager menuManager) { - ImageDescriptor placeHolder = UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png"); + ImageDescriptor placeHolder = CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK); MenuActionFactory.createMenuAction(menuManager, Messages.menu_whatIsNew, placeHolder, handlerService, "com.microsoft.copilot.eclipse.commands.showWhatIsNew", true); } @@ -499,7 +496,7 @@ private void addAuthenticationActions(MenuManager menuManager) { handlerService, "com.microsoft.copilot.eclipse.commands.configureCopilotSettings", true); } // Only show sign out action when the user is in OK, NOT_AUTHORIZED, WARNING, or ERROR state. - ImageDescriptor signOutIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/signout.png"); + ImageDescriptor signOutIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNOUT); MenuActionFactory.createMenuAction(menuManager, Messages.menu_signOutOfGitHub, signOutIcon, handlerService, "com.microsoft.copilot.eclipse.commands.signOut", true); } @@ -607,7 +604,7 @@ protected IStatus run(IProgressMonitor monitor) { if (this.uiElement == null) { throw new IllegalStateException("UI element is not set. Spinner cannot be set."); } - setIconOnDisplayThread(this.uiElement, String.format("/icons/spinner/%d.png", currentIconIndex)); + setIconOnDisplayThread(this.uiElement, CopilotImages.getSpinnerFrameDescriptor(currentIconIndex)); currentIconIndex = (currentIconIndex % TOTAL_SPINNER_ICONS) + 1; if (CopilotCore.getPlugin().getAuthStatusManager() != null && CopilotCore.getPlugin().getAuthStatusManager().isLoading()) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java index 89635a03..1ef1ff15 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java @@ -19,6 +19,7 @@ import org.eclipse.ui.texteditor.ITextEditor; import com.microsoft.copilot.eclipse.core.CopilotCore; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -41,7 +42,7 @@ protected IContributionItem[] getContributionItems() { // Create submenu for Copilot tool configuration MenuManager submenu = new MenuManager("Copilot", - UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"), + CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT), "com.microsoft.copilot.eclipse.ui.toolConfigMenu"); // Add "Configure Tools..." command @@ -49,7 +50,7 @@ protected IContributionItem[] getContributionItems() { CommandContributionItemParameter param = new CommandContributionItemParameter(window, null, "com.microsoft.copilot.eclipse.commands.configureTools", CommandContributionItem.STYLE_PUSH); param.label = "Configure Tools..."; - param.icon = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/tools.png"); + param.icon = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_TOOLS); submenu.add(new CommandContributionItem(param)); items.add(submenu); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java index 557503c0..ddaea8f2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java @@ -15,7 +15,7 @@ import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; /** * Action menu for accepting/rejecting suggestions. @@ -68,8 +68,8 @@ public void show(int x, int y) { String acceptLabel = buildAlignedMenuLabel(Messages.actionMenu_accept, "Tab"); String rejectLabel = buildAlignedMenuLabel(Messages.actionMenu_reject, "Esc"); - ImageDescriptor acceptImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/keyboard-tab.png"); - ImageDescriptor cancelImage = UiUtils.buildImageDescriptorFromPngPath("/icons/close.png"); + ImageDescriptor acceptImage = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_KEYBOARD_TAB); + ImageDescriptor cancelImage = CopilotImages.getImageDescriptor(CopilotImages.IMG_CLOSE); menuManager.add(new Action(acceptLabel, acceptImage) { @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java index bc48eb03..00da9d29 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java @@ -32,6 +32,7 @@ import org.eclipse.ui.texteditor.rulers.IContributedRulerColumn; import org.eclipse.ui.texteditor.rulers.RulerColumnDescriptor; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.completion.EditorsManager; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; @@ -263,7 +264,7 @@ private Rectangle getSuggestionIconBounds(int expectedModelLine, int expectedWid private void ensureIcon() { if (icon == null || icon.isDisposed()) { - ImageDescriptor desc = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/gutter-arrow.png"); + ImageDescriptor desc = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_GUTTER_ARROW); if (desc != null) { icon = desc.createImage(true); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java index aa6f8eaa..6b4b9ff9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java @@ -22,6 +22,7 @@ import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -93,21 +94,12 @@ protected Control createDialogArea(Composite parent) { apiKeyText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); apiKeyText.addModifyListener(this::onFieldChanged); - eyeOpenImg = UiUtils - .buildImageFromPngPath(UiUtils.isDarkTheme() ? "/icons/chat/eye_dark.png" : "/icons/chat/eye.png"); - eyeClosedImg = UiUtils.buildImageFromPngPath( - UiUtils.isDarkTheme() ? "/icons/chat/eye_closed_dark.png" : "/icons/chat/eye_closed.png"); + eyeOpenImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE, CopilotImages.IMG_CHAT_EYE_DARK); + eyeClosedImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE_CLOSED, + CopilotImages.IMG_CHAT_EYE_CLOSED_DARK); toggleEyeBtn = new Button(apiKeyRow, SWT.PUSH); toggleEyeBtn.setImage(eyeClosedImg); toggleEyeBtn.addListener(SWT.Selection, e -> togglePasswordVisibility()); - toggleEyeBtn.addDisposeListener(e -> { - if (eyeOpenImg != null && !eyeOpenImg.isDisposed()) { - eyeOpenImg.dispose(); - } - if (eyeClosedImg != null && !eyeClosedImg.isDisposed()) { - eyeClosedImg.dispose(); - } - }); return container; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java index 4d20ddf6..adba9628 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java @@ -24,6 +24,7 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModel; import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelCapabilities; import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -135,23 +136,14 @@ private void createAzureSpecificFields(Composite container) { apiKeyText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); apiKeyText.addModifyListener(this::onFieldChanged); - eyeOpenImg = UiUtils - .buildImageFromPngPath(UiUtils.isDarkTheme() ? "/icons/chat/eye_dark.png" : "/icons/chat/eye.png"); - eyeClosedImg = UiUtils.buildImageFromPngPath( - UiUtils.isDarkTheme() ? "/icons/chat/eye_closed_dark.png" : "/icons/chat/eye_closed.png"); + eyeOpenImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE, CopilotImages.IMG_CHAT_EYE_DARK); + eyeClosedImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE_CLOSED, + CopilotImages.IMG_CHAT_EYE_CLOSED_DARK); toggleEyeBtn = new Button(apiKeyRow, SWT.FLAT); toggleEyeBtn.setImage(eyeClosedImg); toggleEyeBtn.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); toggleEyeBtn.addListener(SWT.Selection, e -> togglePasswordVisibility()); - toggleEyeBtn.addDisposeListener(e -> { - if (eyeOpenImg != null && !eyeOpenImg.isDisposed()) { - eyeOpenImg.dispose(); - } - if (eyeClosedImg != null && !eyeClosedImg.isDisposed()) { - eyeClosedImg.dispose(); - } - }); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java index 1392a529..fbcc7cb7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java @@ -49,10 +49,10 @@ import com.microsoft.copilot.eclipse.core.FeatureFlags; import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModel; import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ByokService; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * BYOK (Bring Your Own Key) preference page for configuring AI model providers. This page allows users to add, remove, @@ -269,16 +269,9 @@ private Composite createContentArea(Composite parent) { viewerStack.setLayout(stackLayout); GridData stackData = new GridData(SWT.FILL, SWT.FILL, true, true); viewerStack.setLayoutData(stackData); - enabledIcon = UiUtils.buildImageFromPngPath("/icons/chat/keep.png"); + enabledIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP); disabledIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ETOOL_DELETE); - viewerStack.addDisposeListener(e -> { - if (enabledIcon != null && !enabledIcon.isDisposed()) { - enabledIcon.dispose(); - enabledIcon = null; - } - }); - // Create tree viewer composite treeComposite = createTreeViewer(viewerStack); // Create loading overlay diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java index c41975d0..6c5a7ad0 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java @@ -70,6 +70,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.RegistryAccess; import com.microsoft.copilot.eclipse.core.lsp.protocol.LanguageModelToolInformation; import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.McpExtensionPointManager; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpRegistryDialog; @@ -403,20 +404,14 @@ private void createExtMcpRegistrationArea(Composite parent) { var service = CopilotUi.getPlugin().getChatServiceManager().getMcpConfigService(); boolean newExtMcpRegFound = service.isNewExtMcpRegFound(); if (newExtMcpRegFound) { - if (redNotice == null || redNotice.isDisposed()) { - redNotice = UiUtils.buildImageFromPngPath("/icons/chat/red_notice.png"); + if (redNotice == null) { + redNotice = CopilotImages.getImage(CopilotImages.IMG_CHAT_RED_NOTICE); } redNoticeLabel = new Label(extMcpTitleComposite, SWT.NONE); redNoticeLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); redNoticeLabel.setImage(redNotice); redNoticeLabel .setToolTipText(com.microsoft.copilot.eclipse.ui.i18n.Messages.chat_actionBar_toolButton_detected_toolTip); - redNoticeLabel.addDisposeListener(e -> { - if (redNotice != null && !redNotice.isDisposed()) { - redNotice.dispose(); - redNotice = null; - } - }); } // Title label @@ -450,11 +445,6 @@ public void disposeNoticeIcon() { redNoticeLabel = null; } - if (redNotice != null && !redNotice.isDisposed()) { - redNotice.dispose(); - redNotice = null; - } - // Refresh layout to reflect changes immediately extMcpTitleComposite.requestLayout(); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java index 283b736f..98d0556e 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java @@ -28,6 +28,7 @@ import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; @@ -47,7 +48,6 @@ public enum Feature { private Composite selectedCard; private Color normalBackgroundColor; private Color selectedBackgroundColor; - private Image currentContentImage; private Display display; private boolean isDarkTheme; @@ -179,12 +179,12 @@ private void createLeftPanel(Composite parent) { leftPanel.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-container"); // Create clickable feature cards (Agent selected by default) - selectedCard = createClickableFeatureCard(leftPanel, "/icons/github_copilot.png", Messages.quickStart_agent_title, - Messages.quickStart_agent_description, Feature.AGENT); - createClickableFeatureCard(leftPanel, "/icons/chat/chatview_icon_chat.png", Messages.quickStart_ask_title, - Messages.quickStart_ask_description, Feature.ASK); - createClickableFeatureCard(leftPanel, "/icons/chat/chatview_icon_code.png", Messages.quickStart_completion_title, - Messages.quickStart_completion_description, Feature.COMPLETION); + selectedCard = createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT), + Messages.quickStart_agent_title, Messages.quickStart_agent_description, Feature.AGENT); + createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CHAT), + Messages.quickStart_ask_title, Messages.quickStart_ask_description, Feature.ASK); + createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CODE), + Messages.quickStart_completion_title, Messages.quickStart_completion_description, Feature.COMPLETION); } private void createRightPanel(Composite parent) { @@ -198,11 +198,6 @@ private void createRightPanel(Composite parent) { // Content area that will change based on selection rightPanelContent = new Label(rightPanel, SWT.WRAP | SWT.CENTER); rightPanelContent.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card"); - rightPanelContent.addDisposeListener(e -> { - if (currentContentImage != null && !currentContentImage.isDisposed()) { - currentContentImage.dispose(); - } - }); // Add resize listener to properly size the content rightPanel.addListener(SWT.Resize, e -> { @@ -213,7 +208,7 @@ private void createRightPanel(Composite parent) { updateRightPanelContent(Feature.AGENT); } - private Composite createClickableFeatureCard(Composite parent, String imagePath, String title, String description, + private Composite createClickableFeatureCard(Composite parent, Image iconImage, String title, String description, Feature feature) { // Create a canvas for custom rounded painting Canvas cardCanvas = new Canvas(parent, SWT.NONE); @@ -284,8 +279,7 @@ public void mouseDown(MouseEvent e) { iconTitleComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); iconTitleComposite.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card"); - // Icon - Load image from path using UiUtils - Image iconImage = UiUtils.buildImageFromPngPath(imagePath); + // Icon Label iconLabel = new Label(iconTitleComposite, SWT.NONE); iconLabel.setImage(iconImage); iconLabel.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card"); @@ -294,13 +288,6 @@ public void mouseDown(MouseEvent e) { iconData.heightHint = 20; iconLabel.setLayoutData(iconData); - // Dispose image when label is disposed - iconLabel.addDisposeListener(e -> { - if (iconImage != null && !iconImage.isDisposed()) { - iconImage.dispose(); - } - }); - // Make icon clickable too addClickableToControls(clickHandler, iconLabel); @@ -434,28 +421,20 @@ private void updateChildrenControlBackground(Control control, boolean selected) } private void updateRightPanelContent(Feature feature) { - // Dispose previous image if it exists - if (currentContentImage != null && !currentContentImage.isDisposed()) { - currentContentImage.dispose(); - currentContentImage = null; - } - - String imagePath; + String key; switch (feature) { case ASK: - imagePath = "/intro/quickstart/quick_start_ask.png"; + key = CopilotImages.IMG_QUICKSTART_ASK; break; case COMPLETION: - imagePath = "/intro/quickstart/quick_start_completion.png"; + key = CopilotImages.IMG_QUICKSTART_COMPLETION; break; case AGENT: default: - imagePath = "/intro/quickstart/quick_start_agent.png"; + key = CopilotImages.IMG_QUICKSTART_AGENT; } - // Load and set the new image - currentContentImage = UiUtils.buildImageFromPngPath(imagePath); - rightPanelContent.setImage(currentContentImage); + rightPanelContent.setImage(CopilotImages.getImage(key)); rightPanelContent.requestLayout(); } @@ -483,8 +462,8 @@ private void disposeColors() { * Creates a close button with hover effects. */ private Label createCloseButton(Composite parent) { - Image normalImage = UiUtils - .buildImageFromPngPath(isDarkTheme ? "/intro/quickstart/close_dark.png" : "/intro/quickstart/close_light.png"); + Image normalImage = CopilotImages.getThemedImage( + CopilotImages.IMG_QUICKSTART_CLOSE_LIGHT, CopilotImages.IMG_QUICKSTART_CLOSE_DARK); Label closeButton = new Label(parent, SWT.NONE); closeButton.setImage(normalImage); closeButton.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-container"); @@ -501,17 +480,8 @@ public void mouseDown(MouseEvent e) { } }); - Image hoverImage = UiUtils.buildImageFromPngPath( - isDarkTheme ? "/intro/quickstart/close_hover_dark.png" : "/intro/quickstart/close_hover_light.png"); - // Add dispose listener for close button images - closeButton.addDisposeListener(e -> { - if (normalImage != null && !normalImage.isDisposed()) { - normalImage.dispose(); - } - if (hoverImage != null && !hoverImage.isDisposed()) { - hoverImage.dispose(); - } - }); + Image hoverImage = CopilotImages.getThemedImage( + CopilotImages.IMG_QUICKSTART_CLOSE_HOVER_LIGHT, CopilotImages.IMG_QUICKSTART_CLOSE_HOVER_DARK); // Add hover effect for close button closeButton.addMouseTrackListener(new MouseTrackAdapter() { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java index 6799c4a0..6b9d4790 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java @@ -19,9 +19,9 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A custom-painted dropdown button composite. @@ -44,8 +44,6 @@ public class DropdownButton extends Composite { private static final int H_PADDING = 4; private static final int ARROW_AREA_WIDTH = 16; - private static Image arrowIcon; - private final DropdownPopup popup; private List itemGroups; private String selectedItemId; @@ -61,13 +59,6 @@ public DropdownButton(Composite parent, int style) { super(parent, style | SWT.NONE); popup = new DropdownPopup(getShell(), this); - if (arrowIcon == null || arrowIcon.isDisposed()) { - arrowIcon = UiUtils.isDarkTheme() - ? UiUtils.buildImageFromPngPath("/icons/dropdown/down_arrow_dark.png") - : UiUtils.buildImageFromPngPath("/icons/dropdown/down_arrow.png"); - getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons()); - } - addPaintListener(e -> paintControl(e.gc)); addMouseListener(new MouseAdapter() { @@ -158,13 +149,6 @@ public void setAccessibilityName(String name) { AccessibilityUtils.addAccessibilityNameForUiComponent(this, name); } - private static void disposeStaticIcons() { - if (arrowIcon != null && !arrowIcon.isDisposed()) { - arrowIcon.dispose(); - arrowIcon = null; - } - } - private void togglePopup() { if (popup.isOpen()) { popup.close(); @@ -185,6 +169,8 @@ private void openPopup() { private void paintControl(GC gc) { Rectangle bounds = getClientArea(); Display display = getDisplay(); + Image arrowIcon = CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_DOWN_ARROW, + CopilotImages.IMG_DROPDOWN_DOWN_ARROW_DARK); DropdownItem selected = findItemById(selectedItemId); Image selectedIcon = getSelectedItemIcon(selected); Color bg = mouseHover ? CssConstants.getButtonFocusBgColor(display) : getBackground(); @@ -249,9 +235,10 @@ public Point computeSize(int widthHint, int heightHint, boolean changed) { if (selectedIcon != null) { iconWidth = selectedIcon.getBounds().width + ICON_TEXT_GAP; } - Image arrow = arrowIcon; - int arrowWidth = arrowIcon != null && !arrowIcon.isDisposed() - ? arrowIcon.getBounds().width : ARROW_AREA_WIDTH; + Image arrow = CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_DOWN_ARROW, + CopilotImages.IMG_DROPDOWN_DOWN_ARROW_DARK); + int arrowWidth = arrow != null && !arrow.isDisposed() + ? arrow.getBounds().width : ARROW_AREA_WIDTH; int width = H_PADDING + iconWidth + textExtent.x + arrowWidth; int height = getContentHeight(textExtent, selectedIcon, arrow) + 2 * UiConstants.BTN_PADDING; if (widthHint != SWT.DEFAULT) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java index 344eb79c..7f564f53 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java @@ -29,8 +29,8 @@ import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A generic popup shell for {@link DropdownButton}. @@ -51,8 +51,6 @@ class DropdownPopup { private static final int MAX_VISIBLE_ITEMS = 15; private static final int SHORT_POPUP_WIDTH = 250; - private static Image checkIcon; - private Shell shell; private final Shell parentShell; private Consumer selectionListener; @@ -81,26 +79,12 @@ private record ItemEntry(DropdownItem item, Composite composite, ItemController this.parentShell = parentShell; this.anchorControl = anchorControl; this.stylingEngine = PlatformUI.getWorkbench().getService(IStylingEngine.class); - - if (checkIcon == null || checkIcon.isDisposed()) { - checkIcon = UiUtils.isDarkTheme() - ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status_dark.png") - : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status.png"); - parentShell.getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons()); - } } void setSelectionListener(Consumer listener) { this.selectionListener = listener; } - private static void disposeStaticIcons() { - if (checkIcon != null && !checkIcon.isDisposed()) { - checkIcon.dispose(); - checkIcon = null; - } - } - /** * Opens the popup at the given screen location with the provided item groups. * @@ -528,8 +512,9 @@ public void mouseDown(MouseEvent e) { } private Image resolvePopupLeadingIcon(DropdownItem item, boolean selected) { - if (selected && checkIcon != null && !checkIcon.isDisposed()) { - return checkIcon; + if (selected) { + return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS, + CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS_DARK); } Image icon = item.getIcon(); return icon != null && !icon.isDisposed() ? icon : null; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java index 425a6b54..011db3ea 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java @@ -28,11 +28,11 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel.CopilotModelCapabilitiesSupports; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel.CopilotModelCustomModel; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ModelService; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.utils.ModelUtils; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * Renders the full hover UI for model items in the model picker dropdown. The layout consists of the bold title header, @@ -49,8 +49,6 @@ public class ModelHoverContentProvider implements IDropdownItemHoverProvider { /** Vertical padding inside a thinking effort row, so the hover background has breathing room. */ private static final int THINKING_EFFORT_ROW_V_PADDING = 2; - private static Image effortCheckIcon; - private final CopilotModel model; private final IStylingEngine stylingEngine; @@ -213,7 +211,7 @@ private void addThinkingEffortOption(Composite parent, ModelService modelService row.setLayout(rowLayout); row.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); - Image checkIcon = getCheckIcon(parent); + Image checkIcon = getCheckIcon(); Label iconLabel = new Label(row, SWT.NONE); GridData iconGd = new GridData(SWT.LEFT, SWT.CENTER, false, false); if (checkIcon != null) { @@ -317,28 +315,14 @@ private Composite createKeyValueRow(Composite parent) { return row; } - private static void disposeStaticIcons() { - if (effortCheckIcon != null && !effortCheckIcon.isDisposed()) { - effortCheckIcon.dispose(); - effortCheckIcon = null; - } - } - /** * Returns the cached check-mark image used to indicate the selected thinking effort row, lazily loaded on first * access. The icon shares the asset used by the dropdown popup so the leading column lines up visually with the * checkmarks shown next to selected model items. */ - private static Image getCheckIcon(Composite parent) { - if (effortCheckIcon == null || effortCheckIcon.isDisposed()) { - effortCheckIcon = UiUtils.isDarkTheme() - ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status_dark.png") - : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status.png"); - if (parent != null && !parent.isDisposed()) { - parent.getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons()); - } - } - return effortCheckIcon; + private static Image getCheckIcon() { + return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS, + CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS_DARK); } private static boolean isPositive(Integer value) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java index 7ebafdb5..4fb7b9a2 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java @@ -7,7 +7,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; /** * Drives a rotating spinner animation on a target {@link Label}. @@ -21,8 +21,8 @@ * running frame is freed automatically when the label goes away. */ public final class SpinnerAnimator { - /** Total number of frames in the spinner animation under {@code /icons/spinner/}. */ - private static final int TOTAL_FRAMES = 8; + /** Total number of frames. */ + private static final int TOTAL_FRAMES = CopilotImages.SPINNER_FRAMES.length; /** Per-frame interval in milliseconds. */ private static final int FRAME_INTERVAL_MS = 100; @@ -58,10 +58,6 @@ public void run() { if (target.isDisposed()) { return; } - // Dispose the previous frame before loading the next one. - if (currentFrameImage != null && !currentFrameImage.isDisposed()) { - currentFrameImage.dispose(); - } currentFrameImage = buildFrame(currentFrame); target.setImage(currentFrameImage); // Request layout so the icon scale stays correct as frames change. @@ -88,13 +84,10 @@ public void stop() { if (!target.isDisposed() && target.getImage() == currentFrameImage) { target.setImage(null); } - if (currentFrameImage != null && !currentFrameImage.isDisposed()) { - currentFrameImage.dispose(); - } currentFrameImage = null; } private static Image buildFrame(int frame) { - return UiUtils.buildImageFromPngPath(String.format("/icons/spinner/%d.png", frame)); + return CopilotImages.getSpinnerFrame(frame); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java index debbcbef..ee4ff4df 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java @@ -18,6 +18,7 @@ import com.microsoft.copilot.eclipse.core.lsp.mcp.RegistryAccess; import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Icon; import com.microsoft.copilot.eclipse.core.lsp.protocol.NullParams; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.dialogs.mcp.Messages; import com.microsoft.copilot.eclipse.ui.preferences.CopilotPreferenceInitializer; @@ -157,11 +158,11 @@ public static CompletableFuture loadServerIcon(String iconUrl, int width, } /** - * Builds the default MCP server icon image. + * Loads the shared default MCP server icon. * - * @return the default MCP icon image, or {@code null} if it cannot be loaded + * @return the default MCP icon image; callers must not dispose it */ public static Image loadDefaultServerIcon() { - return UiUtils.buildImageFromPngPath("/icons/mcp/mcp_default_icon.png"); + return CopilotImages.getImage(CopilotImages.IMG_MCP_DEFAULT_ICON); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java index 7af8ba99..cf24b437 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java @@ -19,6 +19,7 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CheckQuotaResult; import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan; import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.i18n.Messages; /** @@ -82,19 +83,19 @@ public static double calculatePercentRemaining(CheckQuotaResult quotaStatus) { */ public static ImageDescriptor getUsageIcon(double percentRemaining) { if (percentRemaining <= 10) { - return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_red.png"); + return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_RED); } if (percentRemaining <= 25) { - return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_yellow.png"); + return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_YELLOW); } - return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_blue.png"); + return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_BLUE); } /** * Returns the shared blank icon descriptor used for indented usage rows. */ public static ImageDescriptor getBlankIcon() { - return UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png"); + return CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK); } /** From 99e83346795cace87d9217f9618a1c7b92482766 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 21 Jul 2026 11:43:37 +0200 Subject: [PATCH 3/6] Use image registries to access shared images from other plug-ins (#364) Re-direct calls of PlatformUI.getWorkbench().getSharedImages().getImage(...) to the image registry. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../microsoft/copilot/eclipse/ui/jobs/views/JobsView.java | 7 +++---- .../com/microsoft/copilot/eclipse/ui/chat/ActionBar.java | 2 +- .../copilot/eclipse/ui/chat/AgentStatusLabel.java | 2 +- .../com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java | 4 ++-- .../microsoft/copilot/eclipse/ui/chat/ReferencedFile.java | 3 +-- .../copilot/eclipse/ui/chat/SourceViewerComposite.java | 6 +++--- .../microsoft/copilot/eclipse/ui/chat/StaticBanner.java | 6 +++--- .../com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java | 4 ++-- .../microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java | 4 ++-- .../copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java | 2 +- .../copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java | 2 +- .../eclipse/ui/preferences/AutoApprovePreferencePage.java | 5 ++--- .../copilot/eclipse/ui/preferences/ByokPreferencePage.java | 2 +- .../eclipse/ui/preferences/GlobalAutoApproveSection.java | 5 ++--- .../copilot/eclipse/ui/preferences/McpPreferencePage.java | 4 ++-- 15 files changed, 27 insertions(+), 31 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java index e5774d3a..a3352d73 100644 --- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java +++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java @@ -42,7 +42,6 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection; @@ -568,13 +567,13 @@ private Image getStatusIconForLabel(String nodeText) { return directoryIcon; } else { // This is an error message or unknown status, use warning icon - return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK); + return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK); } } private Image getStatusIconForPr(GitHubPullRequestItem pr) { if (pr == null || pr.copilotWorkStatus() == null) { - return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK); + return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK); } switch (pr.copilotWorkStatus()) { @@ -584,7 +583,7 @@ private Image getStatusIconForPr(GitHubPullRequestItem pr) { } return this.completeIcon; case error: - return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK); + return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK); case in_progress: default: if (this.loadingIcon == null) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java index 21654ec0..adb22ed9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java @@ -862,7 +862,7 @@ private void updateButtonState(SendOrCancelButtonStates state) { break; case CANCEL_ENABLED: isSendButton = false; - Image cancelImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ELCL_STOP); + Image cancelImage = CopilotImages.getSharedImage(ISharedImages.IMG_ELCL_STOP); updateSendOrCancelMsgBtn(true, cancelImage, Messages.chat_actionBar_cancelButton_Tooltip); updateSendToJobBtn(false); break; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java index d61363b0..a19cc3be 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java @@ -103,7 +103,7 @@ public void setErrorStatus() { if (this.status == Status.RUNNING) { spinner.stop(); } - iconLabel.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK)); + iconLabel.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_ERROR_TSK)); this.status = Status.ERROR; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java index 9debb663..26b7fb18 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java @@ -9,8 +9,8 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.swt.WrapLabel; /** @@ -40,7 +40,7 @@ private void buildWarnLabelWithIcon(String message) { composite.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false)); Label icon = new Label(composite, SWT.CENTER); - icon.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK)); + icon.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_ERROR_TSK)); WrapLabel label = new WrapLabel(composite, SWT.LEFT); label.setText(message); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java index bbfb0544..050d5d48 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java @@ -24,7 +24,6 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; import org.eclipse.ui.model.WorkbenchLabelProvider; import com.microsoft.copilot.eclipse.core.Constants; @@ -197,7 +196,7 @@ public void getName(AccessibleEvent event) { */ private void setupUnsupportedFileDisplay() { // Set warning icon - lblfileIcon.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK)); + lblfileIcon.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK)); // Set tooltip with model name String modelName = CopilotUi.getPlugin().getChatServiceManager().getModelService().getActiveModel() .getModelName(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java index b1e737a8..fb2b3f1f 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java @@ -29,13 +29,13 @@ import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; @@ -171,7 +171,7 @@ private Composite createActionsComposite() { layout.pack = true; // Pack the composite tightly result.setLayout(layout); - this.copyIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY); + this.copyIcon = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_COPY); Button copyButton = createActionButton(result, SWT.PUSH | SWT.FLAT, copyIcon, "Copy", "Copy to clipboard"); copyButton.addListener(SWT.Selection, e -> { String content = this.sourceViewer.getDocument().get(); @@ -184,7 +184,7 @@ private Composite createActionsComposite() { ImageDescriptor insertDesc = AbstractUIPlugin.imageDescriptorFromPlugin(UiConstants.WORKBENCH_TEXTEDITOR, UiConstants.INSERT_ICON); this.insertIcon = Optional.ofNullable(insertDesc).map(desc -> desc.createImage()).orElse(null); - Image pasteIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_PASTE); + Image pasteIcon = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_PASTE); Image insertButtonIcon = this.insertIcon != null ? this.insertIcon : pasteIcon; Button insertButton = createActionButton(result, SWT.PUSH | SWT.FLAT, insertButtonIcon, "Insert", "Insert into editor"); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java index ec340b7e..26a257af 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java @@ -18,8 +18,8 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -64,7 +64,7 @@ public StaticBanner(Composite parent, int style, String message, List Date: Tue, 21 Jul 2026 14:14:52 +0200 Subject: [PATCH 4/6] Simplify WrappableIconLink to take only an Image parameter (#364) Drop the icon-path/shared-image factory variants of WrappableIconLink in favor of a single create(...) that accepts an already-resolved Image. Callers now pass registry images (CopilotImages.IMG_INFORMATION) or shared workbench images directly, and the widget no longer owns or disposes the icon. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../AutoApprovePreferencePage.java | 2 +- .../ui/preferences/ByokPreferencePage.java | 2 +- .../CustomModesPreferencePage.java | 3 +- .../ui/preferences/McpPreferencePage.java | 6 +- .../ui/preferences/WrappableIconLink.java | 63 +++++-------------- 5 files changed, 22 insertions(+), 54 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java index ac25e40d..2495a5bd 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java @@ -47,7 +47,7 @@ public void init(IWorkbench workbench) { protected Control createContents(Composite parent) { FeatureFlags flags = CopilotCore.getPlugin().getFeatureFlags(); if (flags != null && !flags.isAutoApprovalEnabled()) { - return WrappableIconLink.createWithSharedImage(parent, + return WrappableIconLink.create(parent, CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_INFO_TSK), Messages.preferences_page_auto_approve_disabled_by_organization); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java index 95b0ac39..feb7e234 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java @@ -177,7 +177,7 @@ protected Control createContents(Composite parent) { disabledCompositeLayout.marginWidth = 0; disabledCompositeLayout.marginHeight = 0; disabledComposite.setLayout(disabledCompositeLayout); - WrappableIconLink.createWithCustomizedImage(disabledComposite, "/icons/information.png", + WrappableIconLink.create(disabledComposite, CopilotImages.getImage(CopilotImages.IMG_INFORMATION), Messages.preferences_page_byok_disabled_tip); contentComposite = createByokView(pageStateStack); updatePageState(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java index 49657b8e..b6a98e20 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java @@ -55,6 +55,7 @@ import com.microsoft.copilot.eclipse.core.chat.CustomChatModeManager; import com.microsoft.copilot.eclipse.core.chat.service.ICustomModeService; import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; +import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; /** @@ -90,7 +91,7 @@ protected Control createContents(Composite parent) { disabledCompositeLayout.marginHeight = 0; disabledComposite.setLayout(disabledCompositeLayout); disabledComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - WrappableIconLink.createWithCustomizedImage(disabledComposite, "/icons/information.png", + WrappableIconLink.create(disabledComposite, CopilotImages.getImage(CopilotImages.IMG_INFORMATION), Messages.setting_disabled_by_organization); return container; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java index f3f2c948..c4424b7b 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java @@ -168,7 +168,7 @@ protected Control createContents(Composite parent) { // Create a simple note for the feature disabled case FeatureFlags flags = CopilotCore.getPlugin().getFeatureFlags(); if (flags != null && !flags.isMcpEnabled()) { - return WrappableIconLink.createWithSharedImage(parent, + return WrappableIconLink.create(parent, CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_INFO_TSK), Messages.preferences_page_mcp_disabled_tip); } @@ -378,8 +378,8 @@ private void showRegistryInfoMessage(String message) { } // Create info message composite using WrappableIconLink - registryInfoMessageComposite = WrappableIconLink.createWithCustomizedImage(mcpRegistryGroup, - "/icons/information.png", message); + registryInfoMessageComposite = WrappableIconLink.create(mcpRegistryGroup, + CopilotImages.getImage(CopilotImages.IMG_INFORMATION), message); // Trigger layout update on the entire hierarchy to ensure proper sizing mcpRegistryGroup.requestLayout(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java index 591f0617..8466405d 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java @@ -5,7 +5,6 @@ import java.net.URL; -import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.SelectionAdapter; @@ -19,11 +18,10 @@ import org.eclipse.ui.PlatformUI; import com.microsoft.copilot.eclipse.core.CopilotCore; -import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** * A composite that displays an icon and a wrappable link. The component automatically resizes when the parent composite - * is resized and handles proper image disposal. + * is resized. The icon image is not owned by this widget and is therefore not disposed by it. */ public class WrappableIconLink extends Composite { @@ -35,61 +33,43 @@ public class WrappableIconLink extends Composite { // Icon private Label iconLabel; - private String iconPath; // only used for customized image case - private Image icon; // holds the created (non-shared) image if any - private boolean isSharedImage; // true when using shared workbench image + private Image icon; // Link private Link linkControl; private String linkText; /** - * Creates a new WrappableIconLink with default layout settings and no icon. + * Creates a new WrappableIconLink. * * @param parent the parent composite + * @param icon the icon image (not owned by this widget) * @param linkText the text for the link (may contain HTML link tags) + * @param widthMargin the horizontal margin to subtract when computing the link width */ - private WrappableIconLink(Composite parent, String iconPath, Image sharedImage, String linkText, int widthMargin) { + private WrappableIconLink(Composite parent, Image icon, String linkText, int widthMargin) { super(parent, SWT.NONE); this.widthMargin = widthMargin; this.parent = parent; this.linkText = linkText; - this.iconPath = iconPath; - this.isSharedImage = sharedImage != null; - this.icon = sharedImage; + this.icon = icon; createControls(); setupResizeListener(); } // ------------- Factory methods ------------- /** - * Creates a WrappableIconLink with a shared workbench image. + * Creates a WrappableIconLink with the given icon image. */ - public static WrappableIconLink createWithSharedImage(Composite parent, Image sharedImage, String linkText) { - return new WrappableIconLink(parent, null, sharedImage, linkText, DEFAULT_MARGIN); + public static WrappableIconLink create(Composite parent, Image icon, String linkText) { + return new WrappableIconLink(parent, icon, linkText, DEFAULT_MARGIN); } /** - * Creates a WrappableIconLink with a shared workbench image and custom width margin. + * Creates a WrappableIconLink with the given icon image and custom width margin. */ - public static WrappableIconLink createWithSharedImage(Composite parent, Image sharedImage, String linkText, - int widthMargin) { - return new WrappableIconLink(parent, null, sharedImage, linkText, widthMargin); - } - - /** - * Creates a WrappableIconLink with a customized image from the given path. - */ - public static WrappableIconLink createWithCustomizedImage(Composite parent, String iconPath, String linkText) { - return new WrappableIconLink(parent, iconPath, null, linkText, DEFAULT_MARGIN); - } - - /** - * Creates a WrappableIconLink with a customized image from the given path and custom width margin. - */ - public static WrappableIconLink createWithCustomizedImage(Composite parent, String iconPath, String linkText, - int widthMargin) { - return new WrappableIconLink(parent, iconPath, null, linkText, widthMargin); + public static WrappableIconLink create(Composite parent, Image icon, String linkText, int widthMargin) { + return new WrappableIconLink(parent, icon, linkText, widthMargin); } /** @@ -103,14 +83,8 @@ private void createControls() { // Create icon label iconLabel = new Label(this, SWT.NONE); iconLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); - if (isSharedImage && icon != null) { - iconLabel.setImage(icon); // shared image - } else if (iconPath != null) { - ImageDescriptor imageDescriptor = UiUtils.buildImageDescriptorFromPngPath(iconPath); - if (imageDescriptor != null) { - icon = imageDescriptor.createImage(); - iconLabel.setImage(icon); - } + if (icon != null) { + iconLabel.setImage(icon); } // Create link control @@ -130,13 +104,6 @@ public void widgetSelected(SelectionEvent e) { } } }); - - // Dispose image when composite is disposed - addDisposeListener(e -> { - if (!isSharedImage && icon != null && !icon.isDisposed()) { - icon.dispose(); - } - }); } /** From 05f8a5279ba21c53a52006a5b0c4d8e29c6e0ce0 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 21 Jul 2026 14:17:59 +0200 Subject: [PATCH 5/6] Remove unused buildImage(Descriptor)FromPngPath helpers (#364) All icon lookups now go through the image registries (CopilotImages / CopilotJobsImages), so the raw PNG-path helpers are no longer referenced. Drop them from the ui-bundle UiUtils and delete the jobs-bundle UiUtils, whose sole purpose was these two methods. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../eclipse/ui/jobs/utils/UiUtils.java | 38 ------------------- .../copilot/eclipse/ui/utils/UiUtils.java | 14 ------- 2 files changed, 52 deletions(-) delete mode 100644 com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java deleted file mode 100644 index fd2220ab..00000000 --- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.ui.jobs.utils; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; - -/** - * Utilities for Eclipse UI in the jobs package. - */ -public class UiUtils { - - private UiUtils() { - // prevent instantiation - } - - /** - * Builds an image descriptor from a PNG file at the given path. - * - * @param path the path to the PNG file - * @return the image descriptor, or null if the resource is not found - */ - public static ImageDescriptor buildImageDescriptorFromPngPath(String path) { - return ImageDescriptor.createFromURL(UiUtils.class.getResource(path)); - } - - /** - * Builds an image from a PNG file at the given path. - * - * @param path the path to the PNG file - * @return the image, or null if the resource is not found - */ - public static Image buildImageFromPngPath(String path) { - ImageDescriptor descriptor = buildImageDescriptorFromPngPath(path); - return descriptor != null ? descriptor.createImage() : null; - } -} diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java index 5d1ab2a9..a7a765fd 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java @@ -406,20 +406,6 @@ public static int modelLine2WidgetLine(ITextViewer viewer, int modelLine) { return modelLine; } - /** - * Builds an image descriptor from a PNG file at the given path. - */ - public static ImageDescriptor buildImageDescriptorFromPngPath(String path) { - return ImageDescriptor.createFromURL(UiUtils.class.getResource(path)); - } - - /** - * Builds an image from a PNG file at the given path. - */ - public static Image buildImageFromPngPath(String path) { - return buildImageDescriptorFromPngPath(path).createImage(); - } - /** * Refreshes the elements of the command with the given ID. */ From 86f6f66554d3acce5aa165bb0a86ecb14d519e55 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 21 Jul 2026 16:00:22 +0200 Subject: [PATCH 6/6] Document image registry usage and disposal rules in copilot instructions (#364) Add an Image Registries subsection under Resource Management. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d33a7203..7c500115 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -301,7 +301,14 @@ CompletableFuture.supplyAsync(() -> { #### Resource Management -**When** creating SWT resources (fonts, images) → dispose them when done; Colors do not need disposal in modern SWT +**When** accessing SWT resources (fonts, images) → prefer accessing them via a plug-in's registry; in that case never dispose them + +**When** accessing SWT resources via a registry is not possible → you must dispose the resources you create +yourself: `Image`, `GC`, `Cursor`, `Region`, `Path`, `Pattern`. `Color` and `Font` are auto-disposed in recent +SWT. Never dispose system colors/fonts, registry-owned images/fonts, or `Display`/`Shell` (framework-managed) + +**When** designing a registry → store `ImageDescriptor`s (not eagerly created `Image`s); the registry creates +and caches the `Image` lazily on first access and disposes it at display shutdown **When** using shared resources → do not dispose resources from other bundles @@ -309,6 +316,18 @@ CompletableFuture.supplyAsync(() -> { **When** using streams/files → use try-with-resources for Eclipse resources (IFile, IDocument) +##### Image Registries + +A plug-in's images can be offered using an `ImageRegistry`. We have `CopilotImages` in +`com.microsoft.copilot.eclipse.ui` and `CopilotJobsImages` in `com.microsoft.copilot.eclipse.ui.jobs`. +The registry loads each image once, caches it, and disposes it automatically when the workbench shuts down. + +**When** accessing *static* images, always prefer using image registries. Do NOT pass image path `"/icons/...png"` +string literals around the code base. + +**When** disposing → do NOT dispose images obtained from a registry or from shared images; the registry owns +them. Only dispose images you constructed yourself with `new Image(...)`. + #### Error Handling **When** reporting errors → use Eclipse `IStatus` and `Status` objects