diff --git a/platform/icons/src/ide/notification/ideUpdate.svg b/platform/icons/src/ide/notification/ideUpdate.svg new file mode 100644 index 000000000000..d89e9fa052bd --- /dev/null +++ b/platform/icons/src/ide/notification/ideUpdate.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/icons/src/ide/notification/ideUpdate_dark.svg b/platform/icons/src/ide/notification/ideUpdate_dark.svg new file mode 100644 index 000000000000..12ee6b26bc10 --- /dev/null +++ b/platform/icons/src/ide/notification/ideUpdate_dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/icons/src/ide/notification/pluginUpdate.svg b/platform/icons/src/ide/notification/pluginUpdate.svg new file mode 100644 index 000000000000..3ea25b928998 --- /dev/null +++ b/platform/icons/src/ide/notification/pluginUpdate.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/icons/src/ide/notification/pluginUpdate_dark.svg b/platform/icons/src/ide/notification/pluginUpdate_dark.svg new file mode 100644 index 000000000000..35033e96f9a5 --- /dev/null +++ b/platform/icons/src/ide/notification/pluginUpdate_dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties index be53f290fc20..c680ed943b9b 100644 --- a/platform/platform-api/resources/messages/IdeBundle.properties +++ b/platform/platform-api/resources/messages/IdeBundle.properties @@ -2403,8 +2403,19 @@ wsl.linux.distribution.label=Linux &distribution: wsl.no.available.distributions=No available distributions name.variable=File name entered in the dialog +action.plugins.text=Plugins... +settings.entry.point.tooltip=IDE and Project Settings +settings.entry.point.update.tooltip=Updates available. IDE and Project Settings +settings.entry.point.update.ide.action=Download {0} {1} +settings.entry.point.update.plugin.action=Update {0} Plugin... +settings.entry.point.update.plugins.action=Update Plugins... +settings.entry.point.update.plugins.more.action=and {0} more template.file.name=Template to generate file name and path template.file.name.optional=Template to generate file name and path (optional) updates.settings.show.editor=Show What's New in the editor after an IDE update plugin.version.bundled=bundled commit.step.user.cancelled=User cancelled +laf.action.install.theme=Install Theme... +keymap.action.configure.keymap=Configure Keymap... +keymap.action.install.keymap=Install Keymap... +settings.entry.point.got.it.popup=Quickly access the main IDE and project settings, and execute commands. diff --git a/platform/platform-api/src/com/intellij/notification/NotificationType.java b/platform/platform-api/src/com/intellij/notification/NotificationType.java index 1981ad829000..23edef107b55 100644 --- a/platform/platform-api/src/com/intellij/notification/NotificationType.java +++ b/platform/platform-api/src/com/intellij/notification/NotificationType.java @@ -19,6 +19,7 @@ package com.intellij.notification; * @author spleaner */ public enum NotificationType { + IDE_UPDATE, INFORMATION, WARNING, ERROR diff --git a/platform/platform-api/src/com/intellij/openapi/wm/WelcomeScreenTab.java b/platform/platform-api/src/com/intellij/openapi/wm/WelcomeScreenTab.java index a9f3dc474997..acc99f0ecd3c 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/WelcomeScreenTab.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/WelcomeScreenTab.java @@ -15,7 +15,7 @@ public interface WelcomeScreenTab { * @return component presents list item on the {@link WelcomeScreen} tab view */ @NotNull - JComponent getKeyComponent(); + JComponent getKeyComponent(@NotNull JList list); /** * @return component shown when related key component is selected diff --git a/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeKeymapAction.java b/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeKeymapAction.java index c022c44de775..95c680f86cca 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeKeymapAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeKeymapAction.java @@ -2,6 +2,7 @@ package com.intellij.ide.actions; import com.intellij.icons.AllIcons; +import com.intellij.ide.IdeBundle; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DefaultActionGroup; @@ -10,7 +11,9 @@ import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.ex.KeymapManagerEx; import com.intellij.openapi.keymap.impl.KeymapManagerImpl; import com.intellij.openapi.keymap.impl.KeymapManagerImplKt; +import com.intellij.openapi.keymap.impl.ui.KeymapPanel; import com.intellij.openapi.keymap.impl.ui.KeymapSchemeManager; +import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; @@ -28,6 +31,14 @@ public final class QuickChangeKeymapAction extends QuickSwitchSchemeAction { for (Keymap keymap : list) { addKeymapAction(group, manager, current, keymap); } + group.addSeparator(); + group.add(new DumbAwareAction(IdeBundle.message("keymap.action.configure.keymap")) { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), KeymapPanel.class); + } + }); + group.add(new ShowPluginsWithSearchOptionAction(IdeBundle.message("keymap.action.install.keymap"), "/tag:Keymap")); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeLookAndFeel.java b/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeLookAndFeel.java index 273af2380149..535c27591b30 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeLookAndFeel.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/QuickChangeLookAndFeel.java @@ -2,6 +2,7 @@ package com.intellij.ide.actions; import com.intellij.icons.AllIcons; +import com.intellij.ide.IdeBundle; import com.intellij.ide.ui.LafManager; import com.intellij.ide.ui.LafManagerListener; import com.intellij.ide.ui.laf.darcula.DarculaInstaller; @@ -27,6 +28,7 @@ import javax.swing.*; public class QuickChangeLookAndFeel extends QuickSwitchSchemeAction { private UIManager.LookAndFeelInfo initialLaf; private final Alarm switchAlarm = new Alarm(); + private boolean myLafActionSelection; @Override protected void fillActions(Project project, @NotNull DefaultActionGroup group, @NotNull DataContext dataContext) { @@ -37,6 +39,9 @@ public class QuickChangeLookAndFeel extends QuickSwitchSchemeAction { for (UIManager.LookAndFeelInfo lf : lfs) { group.add(new LafChangeAction(lf, initialLaf == lf)); } + + group.addSeparator(); + group.add(new ShowPluginsWithSearchOptionAction(IdeBundle.message("laf.action.install.theme"), "/tag:Theme")); } @Override @@ -44,14 +49,17 @@ public class QuickChangeLookAndFeel extends QuickSwitchSchemeAction { switchAlarm.cancelAllRequests(); if (Registry.is("ide.instant.theme.switch")) { popup.addListSelectionListener(event -> { - JList list = (JList)event.getSource(); - Object item = list.getSelectedValue(); + Object item = ((JList)event.getSource()).getSelectedValue(); if (item instanceof AnActionHolder) { - switchAlarm.cancelAllRequests(); - switchAlarm.addRequest(() -> { - LafChangeAction action = (LafChangeAction)((AnActionHolder)item).getAction(); - switchLafAndUpdateUI(LafManager.getInstance(), action.myLookAndFeelInfo, false); - }, Registry.get("ide.instant.theme.switch.delay").asInteger()); + AnAction anAction = ((AnActionHolder)item).getAction(); + myLafActionSelection = anAction instanceof LafChangeAction; + if (myLafActionSelection) { + switchAlarm.cancelAllRequests(); + switchAlarm.addRequest(() -> { + LafChangeAction action = (LafChangeAction)anAction; + switchLafAndUpdateUI(LafManager.getInstance(), action.myLookAndFeelInfo, false); + }, Registry.get("ide.instant.theme.switch.delay").asInteger()); + } } }); } @@ -59,7 +67,7 @@ public class QuickChangeLookAndFeel extends QuickSwitchSchemeAction { popup.addListener(new JBPopupListener() { @Override public void onClosed(@NotNull LightweightWindowEvent event) { - if (Registry.is("ide.instant.theme.switch") && !event.isOk()) { + if (Registry.is("ide.instant.theme.switch") && !event.isOk() && myLafActionSelection) { switchLafAndUpdateUI(LafManager.getInstance(), initialLaf, false); } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/SettingsEntryPointAction.java b/platform/platform-impl/src/com/intellij/ide/actions/SettingsEntryPointAction.java new file mode 100644 index 000000000000..6387b58b4787 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/actions/SettingsEntryPointAction.java @@ -0,0 +1,483 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ide.actions; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.DataManager; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.ide.plugins.PluginStateListener; +import com.intellij.ide.plugins.PluginStateManager; +import com.intellij.ide.plugins.newui.PluginUpdatesService; +import com.intellij.ide.ui.UISettings; +import com.intellij.ide.ui.UISettingsListener; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.CustomComponentAction; +import com.intellij.openapi.actionSystem.ex.TooltipDescriptionProvider; +import com.intellij.openapi.actionSystem.impl.ActionButton; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.updateSettings.impl.CheckForUpdateResult; +import com.intellij.openapi.updateSettings.impl.PluginDownloader; +import com.intellij.openapi.updateSettings.impl.PluginUpdateDialog; +import com.intellij.openapi.updateSettings.impl.UpdateInfoDialog; +import com.intellij.openapi.util.*; +import com.intellij.openapi.wm.*; +import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl; +import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager; +import com.intellij.ui.GotItTooltip; +import com.intellij.util.Consumer; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.update.UiNotifyConnector; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Alexander Lobas + */ +public class SettingsEntryPointAction extends AnAction implements DumbAware, RightAlignedToolbarAction, AnAction.TransparentUpdate, + TooltipDescriptionProvider, CustomComponentAction { + private Icon myIcon; + + public SettingsEntryPointAction() { + initPluginsListeners(); + } + + @Override + public @NotNull JComponent createCustomComponent(@NotNull Presentation presentation, @NotNull String place) { + ActionButton button = new ActionButton(this, presentation, place, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE); + + UiNotifyConnector.doWhenFirstShown(button, () -> { + Disposable disposable = Disposer.newDisposable(); + + Balloon balloon = createGotItTooltip(disposable).showAt(Balloon.Position.below, button, component -> { + PropertiesComponent.getInstance().setValue(GotItTooltip.PROPERTY_PREFIX + ".settings.entry.point", "0"); + return new Point(component.getWidth() / 2, component.getHeight()); + }); + + if (balloon == null) { + Disposer.dispose(disposable); + } + else { + new MyListener(button.getParent(), balloon, disposable); + } + }); + + return button; + } + + private static class MyListener extends ComponentAdapter implements HierarchyListener { + private final Component myComponent; + private final Balloon myBalloon; + private final Disposable myDisposable; + + private MyListener(@NotNull Component component, @NotNull Balloon balloon, @NotNull Disposable disposable) { + myComponent = component; + myBalloon = balloon; + myDisposable = disposable; + + myComponent.addComponentListener(this); + myComponent.addHierarchyListener(this); + } + + @Override + public void componentResized(ComponentEvent e) { + handle(); + } + + @Override + public void componentMoved(ComponentEvent e) { + handle(); + } + + @Override + public void componentHidden(ComponentEvent e) { + handle(); + } + + @Override + public void hierarchyChanged(HierarchyEvent e) { + handle(); + } + + private void handle() { + if (myComponent.isShowing() && !myBalloon.isDisposed()) { + myBalloon.revalidate(); + } + else { + myComponent.removeComponentListener(this); + myComponent.removeHierarchyListener(this); + Disposer.dispose(myDisposable); + } + } + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + myIcon = AllIcons.General.GearPlain; + // XXX update toolbar action + + ListPopup popup = createMainPopup(e.getDataContext()); + popup.addListener(new JBPopupListener() { + @Override + public void onClosed(@NotNull LightweightWindowEvent event) { + myIcon = null; + // XXX update toolbar action + } + }); + + InputEvent inputEvent = e.getInputEvent(); + if (inputEvent == null) { + popup.showInFocusCenter(); + } + else { + Component component = inputEvent.getComponent(); + if (component instanceof ActionButtonComponent) { + popup.showUnderneathOf(component); + } + else { + popup.showInCenterOf(component); + } + } + } + + @Override + public void update(@NotNull AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setDescription(SettingsEntryPointAction::getActionTooltip); + presentation.setIcon(myIcon == null ? getActionIcon() : myIcon); + } + + @NotNull + private static ListPopup createMainPopup(@NotNull DataContext context) { + DefaultActionGroup group = new DefaultActionGroup(); + + if (myPlatformUpdateInfo != null) { + group.add(new DumbAwareAction(IdeBundle.message("settings.entry.point.update.ide.action", + ApplicationNamesInfo.getInstance().getFullProductName(), + myPlatformUpdateInfo.getNewBuild().getVersion()), + null, AllIcons.Ide.Notification.IdeUpdate) { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + if (myPlatformUpdateInfo.getPatches() == null || + (!SystemInfo.isWindows && !Files.isWritable(Paths.get(PathManager.getHomePath())))) { + new UpdateInfoDialog(myPlatformUpdateInfo.getUpdatedChannel(), myPlatformUpdateInfo.getNewBuild(), null, true, null, + myIncompatiblePlugins).show(); + } + else { + CheckForUpdateResult platformUpdateInfo = myPlatformUpdateInfo; + newPlatformUpdate(null, null); + + ActionCallback callback = new ActionCallback().doWhenRejected(() -> { + ApplicationManager.getApplication().invokeLater(() -> { + newPlatformUpdate(platformUpdateInfo, null); + }, ModalityState.any()); + }); + + UpdateInfoDialog.downloadPatchAndRestart(platformUpdateInfo.getNewBuild(), platformUpdateInfo.getUpdatedChannel(), + platformUpdateInfo.getPatches(), null, null, callback); + } + } + }); + } + if (myUpdatedPlugins != null) { + int size = myUpdatedPlugins.size(); + + String name = size == 1 + ? IdeBundle.message("settings.entry.point.update.plugin.action", myUpdatedPlugins.iterator().next().getPluginName()) + : IdeBundle.message("settings.entry.point.update.plugins.action"); + group.add(new DumbAwareAction(name, null, AllIcons.Ide.Notification.PluginUpdate) { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + new PluginUpdateDialog(e.getProject(), myUpdatedPlugins, myCustomRepositoryPlugins).show(); + } + }); + if (size > 1) { + StringBuilder result = new StringBuilder(); + int count = 0; + + for (PluginDownloader plugin : myUpdatedPlugins) { + if (result.length() > 0) { + result.append(", "); + } + result.append(plugin.getPluginName()); + count++; + if (count == 2) { + int delta = size - count; + if (delta > 0) { + result.append(" ").append(IdeBundle.message("settings.entry.point.update.plugins.more.action", delta)); + } + break; + } + } + + @NlsSafe String actionName = result.toString(); + group.add(new AnAction(actionName) { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + } + + @Override + public void update(@NotNull AnActionEvent e) { + e.getPresentation().setEnabled(false); + } + }); + } + } + + group.addSeparator(); + + ActionGroup templateGroup = (ActionGroup)ActionManager.getInstance().getAction("SettingsEntryPointGroup"); + for (AnAction child : templateGroup.getChildren(null)) { + group.add(child); + } + + return JBPopupFactory.getInstance().createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.MNEMONICS, true); + } + + @NotNull + private static GotItTooltip createGotItTooltip(@NotNull Disposable disposable) { + return new GotItTooltip("settings.entry.point", IdeBundle.message("settings.entry.point.got.it.popup"), disposable); + } + + private static PluginUpdatesService myUpdatesService; + private static PluginStateListener myPluginStateListener; + + private static void initPluginsListeners() { + if (myUpdatesService == null) { + myUpdatesService = PluginUpdatesService.connectWithUpdates(descriptors -> { + List downloaders = new ArrayList<>(); + try { + for (IdeaPluginDescriptor descriptor : descriptors) { + downloaders.add(PluginDownloader.createDownloader(descriptor)); + } + } + catch (IOException e) { + PluginManagerCore.getLogger().error(e); + } + newPluginsUpdate(downloaders.isEmpty() ? null : downloaders, null); + }); + } + if (myPluginStateListener == null) { + PluginStateManager.addStateListener(myPluginStateListener = new PluginStateListener() { + @Override + public void install(@NotNull IdeaPluginDescriptor descriptor) { + if (myUpdatedPlugins != null) { + PluginId pluginId = descriptor.getPluginId(); + List updatedPlugins = + ContainerUtil.filter(myUpdatedPlugins, downloader -> !pluginId.equals(downloader.getId())); + if (myUpdatedPlugins.size() != updatedPlugins.size()) { + newPluginsUpdate(updatedPlugins.isEmpty() ? null : updatedPlugins, myCustomRepositoryPlugins); + } + } + } + + @Override + public void uninstall(@NotNull IdeaPluginDescriptor descriptor) { + } + }); + } + } + + private static CheckForUpdateResult myPlatformUpdateInfo; + private static Collection myIncompatiblePlugins; + + private static Collection myUpdatedPlugins; + private static Collection myCustomRepositoryPlugins; + + public static void newPlatformUpdate(@Nullable CheckForUpdateResult platformUpdateInfo, + @Nullable Collection incompatiblePlugins) { + myPlatformUpdateInfo = platformUpdateInfo; + myIncompatiblePlugins = incompatiblePlugins; + updateAction(); + } + + public static void newPluginsUpdate(@Nullable Collection updatedPlugins, + @Nullable Collection customRepositoryPlugins) { + myUpdatedPlugins = updatedPlugins; + myCustomRepositoryPlugins = customRepositoryPlugins; + updateAction(); + } + + private static void updateAction() { + if (isAvailableInStatusBar()) { + updateWidgets(); + } + // XXX update toolbar action + } + + private static @NotNull @Nls String getActionTooltip() { + return myPlatformUpdateInfo == null && myUpdatedPlugins == null + ? IdeBundle.message("settings.entry.point.tooltip") + : IdeBundle.message("settings.entry.point.update.tooltip"); + } + + private static @NotNull Icon getActionIcon() { + if (myPlatformUpdateInfo != null) { + return AllIcons.Ide.Notification.IdeUpdate; + } + if (myUpdatedPlugins != null) { + return AllIcons.Ide.Notification.PluginUpdate; + } + return AllIcons.General.GearPlain; + } + + private static UISettingsListener mySettingsListener; + + private static void initUISettingsListener() { + if (mySettingsListener == null) { + mySettingsListener = uiSettings -> updateWidgets(); + ApplicationManager.getApplication().getMessageBus().connect().subscribe(UISettingsListener.TOPIC, mySettingsListener); + } + } + + private static void updateWidgets() { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + project.getService(StatusBarWidgetsManager.class).updateWidget(StatusBarManager.class); + IdeFrame frame = WindowManager.getInstance().getIdeFrame(project); + if (frame != null) { + StatusBar statusBar = frame.getStatusBar(); + if (statusBar != null) { + statusBar.updateWidget(WIDGET_ID); + } + } + } + } + + private static boolean isAvailableInStatusBar() { + initUISettingsListener(); + + UISettings settings = UISettings.getInstance(); + return !settings.getShowMainToolbar() && !settings.getShowToolbarInNavigationBar(); + } + + private static final String WIDGET_ID = "settingsEntryPointWidget"; + + public static class StatusBarManager implements StatusBarWidgetFactory { + @Override + public @NonNls @NotNull String getId() { + return WIDGET_ID; + } + + @Override + public @Nls @NotNull String getDisplayName() { + return IdeBundle.message("settings.entry.point.tooltip"); + } + + @Override + public boolean isAvailable(@NotNull Project project) { + return isAvailableInStatusBar(); + } + + @Override + public @NotNull StatusBarWidget createWidget(@NotNull Project project) { + return new MyStatusBarWidget(); + } + + @Override + public void disposeWidget(@NotNull StatusBarWidget widget) { + Disposer.dispose(widget); + } + + @Override + public boolean canBeEnabledOn(@NotNull StatusBar statusBar) { + return isAvailableInStatusBar(); + } + + @Override + public boolean isConfigurable() { + return false; + } + } + + private static class MyStatusBarWidget implements StatusBarWidget, StatusBarWidget.IconPresentation { + private StatusBar myStatusBar; + private Icon myIcon; + + private MyStatusBarWidget() { + initPluginsListeners(); + } + + @Override + public @NonNls @NotNull String ID() { + return WIDGET_ID; + } + + @Override + public void install(@NotNull StatusBar statusBar) { + myStatusBar = statusBar; + + JComponent component = ((IdeStatusBarImpl)statusBar).getWidgetComponent(WIDGET_ID); + if (component != null) { + createGotItTooltip(this).showAt(Balloon.Position.above, component, c -> new Point(c.getWidth() / 2, 0)); + } + } + + @Override + public @Nullable WidgetPresentation getPresentation() { + return this; + } + + @Override + public @Nullable @NlsContexts.Tooltip String getTooltipText() { + return getActionTooltip(); + } + + @Override + public @Nullable Consumer getClickConsumer() { + return event -> { + myIcon = AllIcons.General.GearPlain; + myStatusBar.updateWidget(WIDGET_ID); + + Component component = event.getComponent(); + ListPopup popup = createMainPopup(DataManager.getInstance().getDataContext(component)); + popup.addListener(new JBPopupListener() { + @Override + public void beforeShown(@NotNull LightweightWindowEvent event) { + Point location = component.getLocationOnScreen(); + Dimension size = popup.getSize(); + popup.setLocation(new Point(location.x + component.getWidth() - size.width, location.y - size.height)); + } + + @Override + public void onClosed(@NotNull LightweightWindowEvent event) { + myIcon = null; + myStatusBar.updateWidget(WIDGET_ID); + } + }); + popup.show(component); + }; + } + + @Override + public @Nullable Icon getIcon() { + return myIcon == null ? getActionIcon() : myIcon; + } + + @Override + public void dispose() { + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ShowPluginsWithSearchOptionAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ShowPluginsWithSearchOptionAction.java new file mode 100644 index 000000000000..fb789f83733a --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/actions/ShowPluginsWithSearchOptionAction.java @@ -0,0 +1,26 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ide.actions; + +import com.intellij.ide.plugins.PluginManagerConfigurable; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.DumbAwareAction; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * @author Alexander Lobas + */ +public class ShowPluginsWithSearchOptionAction extends DumbAwareAction { + private final String mySearchOption; + + public ShowPluginsWithSearchOptionAction(@NotNull @Nls String text, @NotNull String searchOption) { + super(text); + mySearchOption = searchOption; + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), PluginManagerConfigurable.class, c -> c.enableSearch(mySearchOption)); + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java index 0f99ee9b700c..599d6fa642dd 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java @@ -132,6 +132,8 @@ public class PluginManagerConfigurable private Collection myInitUpdates; + private String myLaterSearchQuery; + public PluginManagerConfigurable(@Nullable Project project) { myPluginModel = new MyPluginModel(project); } @@ -203,7 +205,7 @@ public class PluginManagerConfigurable if (myInitUpdates != null) { callback.accept(myInitUpdates.size()); } - myPluginUpdatesService = PluginUpdatesService.connectConfigurable(callback); + myPluginUpdatesService = PluginUpdatesService.connectWithCounter(callback); myPluginModel.setPluginUpdatesService(myPluginUpdatesService); boolean selectInstalledTab = !ContainerUtil.isEmpty(myInitUpdates); @@ -236,6 +238,14 @@ public class PluginManagerConfigurable myInstalledTab.setSearchQuery("/outdated"); } + if (myLaterSearchQuery != null) { + Runnable search = enableSearch(myLaterSearchQuery); + if (search != null) { + ApplicationManager.getApplication().invokeLater(search, ModalityState.any()); + } + myLaterSearchQuery = null; + } + return myCardPanel; } @@ -893,7 +903,7 @@ public class PluginManagerConfigurable myPluginModel.addEnabledGroup(group); } - myPluginUpdatesService.connectInstalled(updates -> { + myPluginUpdatesService.calculateUpdates(updates -> { if (ContainerUtil.isEmpty(updates)) { clearUpdates(myInstalledPanel); clearUpdates(myInstalledSearchPanel.getPanel()); @@ -1157,6 +1167,10 @@ public class PluginManagerConfigurable }; myPluginModel.setCancelInstallCallback(descriptor -> { + if (myInstalledSearchPanel == null) { + return; + } + PluginsGroup group = myInstalledSearchPanel.getGroup(); if (group.ui != null && group.ui.findComponent(descriptor) != null) { @@ -1721,6 +1735,10 @@ public class PluginManagerConfigurable @Nullable @Override public Runnable enableSearch(String option) { + if (myTabHeaderComponent == null) { + myLaterSearchQuery = option; + return null; + } if (StringUtil.isEmpty(option) && (myTabHeaderComponent.getSelectionTab() == MARKETPLACE_TAB || myInstalledSearchPanel.isEmpty())) { return null; } @@ -1770,4 +1788,4 @@ public class PluginManagerConfigurable }); } } -} +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableTreeRenderer.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableTreeRenderer.java index bcdae572d331..ce44010a3ab1 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableTreeRenderer.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableTreeRenderer.java @@ -44,14 +44,12 @@ public class PluginManagerConfigurableTreeRenderer extends AncestorListenerAdapt @Nullable UnnamedConfigurable configurable, boolean selected) { if (myTree == null) { - myService = PluginUpdatesService.connectTreeRenderer(this); + myService = PluginUpdatesService.connectWithCounter(this); tree.addAncestorListener(this); myTree = tree; } - Icon icon = DynamicBundle.LanguageBundleEP.EP_NAME.hasAnyExtensions() ? - AllIcons.General.LocalizationSettings : - null; + Icon icon = DynamicBundle.LanguageBundleEP.EP_NAME.hasAnyExtensions() ? AllIcons.General.LocalizationSettings : null; if (icon == null && myCountValue == null) { return null; } @@ -61,16 +59,11 @@ public class PluginManagerConfigurableTreeRenderer extends AncestorListenerAdapt myExtraLabel.setIcon(icon); myExtraLabel.setBackground(myCountLabel.getBackground()); - Component component = icon == null ? - myCountLabel : - myCountValue == null ? - myExtraLabel : - myPanel; + Component component = icon == null ? myCountLabel : myCountValue == null ? myExtraLabel : myPanel; boolean isPanel = component == myPanel; return Pair.create( - component, - (renderer, bounds, text, right, textBaseline) -> { + component, (renderer, bounds, text, right, textBaseline) -> { Dimension size = renderer.getPreferredSize(); int preferredWidth = size.width; int preferredHeight = size.height; @@ -101,9 +94,7 @@ public class PluginManagerConfigurableTreeRenderer extends AncestorListenerAdapt } private int getPreferredShift(boolean isPanel) { - return isPanel ? - getHGap() + myExtraLabel.getPreferredSize().width / 2 : - 0; + return isPanel ? getHGap() + myExtraLabel.getPreferredSize().width / 2 : 0; } private static int getHGap() { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginUpdatesService.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginUpdatesService.java index f2fb995a4098..dbe004340d52 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginUpdatesService.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginUpdatesService.java @@ -13,7 +13,6 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -25,124 +24,109 @@ import java.util.function.Consumer; */ public class PluginUpdatesService { private static final List SERVICES = new ArrayList<>(); + private static final Object ourLock = new Object(); private static Collection myCache; private static boolean myPrepared; private static boolean myPreparing; private static boolean myReset; - private Consumer myTreeCallback; - private Consumer myTabCallback; - private Consumer> myInstalledPanelCallback; - private Consumer> myUpdatePanelCallback; + private Consumer myCountCallback; + private Consumer> myUpdateCallback; @NotNull - public static PluginUpdatesService connectTreeRenderer(@NotNull Consumer callback) { - checkAccess(); - + public static PluginUpdatesService connectWithCounter(@NotNull Consumer callback) { PluginUpdatesService service = new PluginUpdatesService(); - SERVICES.add(service); - service.myTreeCallback = callback; + service.myCountCallback = callback; - if (myPrepared) { - callback.accept(getCount()); - } - else { - calculateUpdates(); + synchronized (ourLock) { + SERVICES.add(service); + + if (myPrepared) { + callback.accept(getCount()); + return service; + } } + calculateUpdates(); return service; } @NotNull - public static PluginUpdatesService connectConfigurable(@NotNull Consumer callback) { - checkAccess(); - + public static PluginUpdatesService connectWithUpdates(@NotNull Consumer> callback) { PluginUpdatesService service = new PluginUpdatesService(); - SERVICES.add(service); - service.myTabCallback = callback; + service.myUpdateCallback = callback; - if (myPrepared) { - callback.accept(getCount()); - } - else { - calculateUpdates(); + synchronized (ourLock) { + SERVICES.add(service); + + if (myPrepared) { + callback.accept(myCache); + } } return service; } - public void connectInstalled(@NotNull Consumer> callback) { - checkAccess(); - myInstalledPanelCallback = callback; - - if (myPrepared) { - callback.accept(myCache); - } - else { - calculateUpdates(); - } - } - public void calculateUpdates(@NotNull Consumer> callback) { - checkAccess(); - myUpdatePanelCallback = callback; + synchronized (ourLock) { + myUpdateCallback = callback; - if (myPrepared) { - callback.accept(myCache); - } - else { - calculateUpdates(); + if (myPrepared) { + callback.accept(myCache); + return; + } } + calculateUpdates(); } public void finishUpdate(@NotNull IdeaPluginDescriptor descriptor) { - checkAccess(); - - if (!myPrepared || myCache == null) { - return; - } - - for (Iterator I = myCache.iterator(); I.hasNext(); ) { - IdeaPluginDescriptor downloadedDescriptor = I.next(); - - if (downloadedDescriptor.equals(descriptor)) { - I.remove(); - - Integer countValue = getCount(); - for (PluginUpdatesService service : SERVICES) { - service.runCountCallbacks(countValue); - } - + synchronized (ourLock) { + if (!myPrepared || myCache == null) { return; } + + for (Iterator I = myCache.iterator(); I.hasNext(); ) { + IdeaPluginDescriptor downloadedDescriptor = I.next(); + + if (downloadedDescriptor.equals(descriptor)) { + I.remove(); + + Integer countValue = getCount(); + for (PluginUpdatesService service : SERVICES) { + service.runCountCallbacks(countValue); + } + + return; + } + } } } public void finishUpdate() { - checkAccess(); + synchronized (ourLock) { + if (!myPrepared || myCache == null) { + return; + } - if (!myPrepared || myCache == null) { - return; - } - - Integer countValue = getCount(); - for (PluginUpdatesService service : SERVICES) { - service.runCountCallbacks(countValue); + Integer countValue = getCount(); + for (PluginUpdatesService service : SERVICES) { + service.runCountCallbacks(countValue); + } } } public void recalculateUpdates() { - checkAccess(); + synchronized (ourLock) { + for (PluginUpdatesService service : SERVICES) { + service.runAllCallbacks(0); + } - for (PluginUpdatesService service : SERVICES) { - service.runAllCallbacks(0); - } - - if (myPreparing) { - resetUpdates(); - } - else { - calculateUpdates(); + if (myPreparing) { + resetUpdates(); + } + else { + calculateUpdates(); + } } } @@ -151,28 +135,30 @@ public class PluginUpdatesService { } public void dispose() { - checkAccess(); dispose(this); } private static void dispose(@NotNull PluginUpdatesService service) { - SERVICES.remove(service); + synchronized (ourLock) { + SERVICES.remove(service); - if (SERVICES.isEmpty()) { - myCache = null; - myPrepared = false; - myPreparing = false; + if (SERVICES.isEmpty()) { + myCache = null; + myPrepared = false; + myPreparing = false; + } } } public static boolean isNeedUpdate(@NotNull IdeaPluginDescriptor descriptor) { - checkAccess(); - PluginId pluginId = descriptor.getPluginId(); - if (myPrepared && myCache != null) { - for (IdeaPluginDescriptor downloader : myCache) { - if (pluginId.equals(downloader.getPluginId())) { - return true; + + synchronized (ourLock) { + if (myPrepared && myCache != null) { + for (IdeaPluginDescriptor downloader : myCache) { + if (pluginId.equals(downloader.getPluginId())) { + return true; + } } } } @@ -182,43 +168,46 @@ public class PluginUpdatesService { @Nullable public static Collection getUpdates() { - checkAccess(); - return !myPrepared || myPreparing || myCache == null ? null : myCache; + synchronized (ourLock) { + return !myPrepared || myPreparing || myCache == null ? null : myCache; + } } private static void calculateUpdates() { - if (myPreparing) { - return; + synchronized (ourLock) { + if (myPreparing) { + return; + } + myPreparing = true; + myCache = null; } - myPreparing = true; - myCache = null; ApplicationManager.getApplication().executeOnPooledThread(() -> { UpdateChecker.CheckPluginsUpdateResult updates = UpdateChecker.checkPluginsUpdate(new EmptyProgressIndicator()); ApplicationManager.getApplication().invokeLater(() -> { - checkAccess(); + synchronized (ourLock) { + myPreparing = false; - myPreparing = false; + if (myReset) { + myReset = false; + calculateUpdates(); + return; + } - if (myReset) { - myReset = false; - calculateUpdates(); - return; - } + myPrepared = true; + List cache = new ArrayList<>(); + Collection availableUpdates = updates.getAvailableUpdates(); + if (availableUpdates != null) { + cache.addAll(ContainerUtil.map(availableUpdates, (downloader -> downloader.getDescriptor()))); + } + cache.addAll(ContainerUtil.map(updates.getAvailableDisabledUpdates(), (downloader -> downloader.getDescriptor()))); + myCache = cache; - myPrepared = true; - List cache = new ArrayList<>(); - Collection availableUpdates = updates.getAvailableUpdates(); - if (availableUpdates != null) { - cache.addAll(ContainerUtil.map(availableUpdates, (downloader -> downloader.getDescriptor()))); - } - cache.addAll(ContainerUtil.map(updates.getAvailableDisabledUpdates(), (downloader -> downloader.getDescriptor()))); - myCache = cache; - - Integer countValue = getCount(); - for (PluginUpdatesService service : SERVICES) { - service.runAllCallbacks(countValue); + Integer countValue = getCount(); + for (PluginUpdatesService service : SERVICES) { + service.runAllCallbacks(countValue); + } } }, ModalityState.any()); }); @@ -227,20 +216,14 @@ public class PluginUpdatesService { private void runAllCallbacks(@Nullable Integer countValue) { runCountCallbacks(countValue); - if (myInstalledPanelCallback != null) { - myInstalledPanelCallback.accept(myCache); - } - if (myUpdatePanelCallback != null) { - myUpdatePanelCallback.accept(myCache); + if (myUpdateCallback != null) { + myUpdateCallback.accept(myCache); } } private void runCountCallbacks(@Nullable Integer countValue) { - if (myTreeCallback != null) { - myTreeCallback.accept(countValue); - } - if (myTabCallback != null) { - myTabCallback.accept(countValue); + if (myCountCallback != null) { + myCountCallback.accept(countValue); } } @@ -248,8 +231,4 @@ public class PluginUpdatesService { private static Integer getCount() { return myCache == null ? null : myCache.size(); } - - private static void checkAccess() { - assert SwingUtilities.isEventDispatchThread(); - } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/notification/EventLogConsole.java b/platform/platform-impl/src/com/intellij/notification/EventLogConsole.java index 69d82a24aee1..4edc7b17fc55 100644 --- a/platform/platform-impl/src/com/intellij/notification/EventLogConsole.java +++ b/platform/platform-impl/src/com/intellij/notification/EventLogConsole.java @@ -260,7 +260,7 @@ final class EventLogConsole { final NotificationType type = notification.getType(); TextAttributesKey key = type == NotificationType.ERROR ? ConsoleViewContentType.LOG_ERROR_OUTPUT_KEY - : type == NotificationType.INFORMATION + : type == NotificationType.INFORMATION || type == NotificationType.IDE_UPDATE ? ConsoleViewContentType.NORMAL_OUTPUT_KEY : ConsoleViewContentType.LOG_WARNING_OUTPUT_KEY; diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationCollector.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationCollector.java index ed962128dd35..d25389ceeb4e 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationCollector.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationCollector.java @@ -62,7 +62,7 @@ public final class NotificationCollector { boolean isExpandable) { List> data = createNotificationData(notification.getGroupId(), notification.id, notification.displayId); data.add(DISPLAY_TYPE.with(displayType)); - data.add(SEVERITY.with(notification.getType())); + data.add(SEVERITY.with(getType(notification))); data.add(IS_EXPANDABLE.with(isExpandable)); SHOWN.log(project, data); } @@ -71,16 +71,21 @@ public final class NotificationCollector { @NotNull Notification notification) { List> data = createNotificationData(notification.getGroupId(), notification.id, notification.displayId); data.add(DISPLAY_TYPE.with(NotificationDisplayType.TOOL_WINDOW)); - data.add(SEVERITY.with(notification.getType())); + data.add(SEVERITY.with(getType(notification))); SHOWN.log(project, data); } public void logNotificationLoggedInEventLog(@NotNull Project project, @NotNull Notification notification) { List> data = createNotificationData(notification.getGroupId(), notification.id, notification.displayId); - data.add(SEVERITY.with(notification.getType())); + data.add(SEVERITY.with(getType(notification))); LOGGED.log(project, data); } + @NotNull + private static NotificationType getType(@NotNull Notification notification) { + return notification.getType() == NotificationType.IDE_UPDATE ? NotificationType.INFORMATION : notification.getType(); + } + public void logNotificationBalloonClosedByUser(@Nullable String notificationId, @Nullable String notificationDisplayId, @Nullable String groupId) { diff --git a/platform/platform-impl/src/com/intellij/notification/impl/widget/IdeNotificationArea.java b/platform/platform-impl/src/com/intellij/notification/impl/widget/IdeNotificationArea.java index e12a720e35f3..a0d5e37ffc8a 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/widget/IdeNotificationArea.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/widget/IdeNotificationArea.java @@ -121,6 +121,7 @@ public class IdeNotificationArea extends JLabel implements CustomStatusBarWidget case ERROR: return forToolWindow ? AllIcons.Toolwindows.ErrorEvents : AllIcons.Ide.Notification.ErrorEvents; case INFORMATION: + case IDE_UPDATE: return forToolWindow ? AllIcons.Toolwindows.InfoEvents : AllIcons.Ide.Notification.InfoEvents; } } @@ -131,14 +132,15 @@ public class IdeNotificationArea extends JLabel implements CustomStatusBarWidget public static NotificationType getMaximumType(List notifications) { NotificationType result = null; for (Notification notification : notifications) { - if (NotificationType.ERROR == notification.getType()) { + NotificationType type = notification.getType(); + if (NotificationType.ERROR == type) { return NotificationType.ERROR; } - if (NotificationType.WARNING == notification.getType()) { + if (NotificationType.WARNING == type) { result = NotificationType.WARNING; } - else if (result == null && NotificationType.INFORMATION == notification.getType()) { + else if (result == null && (NotificationType.INFORMATION == type || NotificationType.IDE_UPDATE == type)) { result = NotificationType.INFORMATION; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginUpdateDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginUpdateDialog.java index fb1dba1d3b15..6679012efc7e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginUpdateDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginUpdateDialog.java @@ -59,7 +59,7 @@ public class PluginUpdateDialog extends DialogWrapper { public PluginUpdateDialog(@Nullable Project project, @NotNull Collection updatedPlugins, - @NotNull Collection customRepositoryPlugins) { + @Nullable Collection customRepositoryPlugins) { super(true); setTitle(IdeBundle.message("dialog.title.plugin.updates")); @@ -82,7 +82,7 @@ public class PluginUpdateDialog extends DialogWrapper { @Override @NotNull protected Collection getCustomRepoPlugins() { - return customRepositoryPlugins; + return customRepositoryPlugins == null ? super.getCustomRepoPlugins() : customRepositoryPlugins; } }; diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt index 71c3b666099a..1635ef35802c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt @@ -3,6 +3,7 @@ package com.intellij.openapi.updateSettings.impl import com.google.common.annotations.VisibleForTesting import com.intellij.ide.IdeBundle +import com.intellij.ide.actions.SettingsEntryPointAction import com.intellij.ide.externalComponents.ExternalComponentManager import com.intellij.ide.plugins.* import com.intellij.ide.plugins.marketplace.BrokenPluginsService @@ -29,7 +30,6 @@ import com.intellij.openapi.util.Pair.pair import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame -import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrameUpdater import com.intellij.reference.SoftReference import com.intellij.util.Urls import com.intellij.util.concurrency.AppExecutorUtil @@ -120,9 +120,7 @@ object UpdateChecker { val fromSettings = customSettings != null ProgressManager.getInstance().run(object : Task.Backgroundable(project, IdeBundle.message("updates.checking.progress"), true) { - override fun run(indicator: ProgressIndicator) = doUpdateAndShowResult(getProject(), !fromSettings, - fromSettings || WelcomeFrame.getInstance() != null, true, - settings, indicator, null) + override fun run(indicator: ProgressIndicator) = doUpdateAndShowResult(getProject(), !fromSettings, fromSettings, true, settings, indicator, null) override fun isConditionalModal(): Boolean = fromSettings override fun shouldStartInBackground(): Boolean = !fromSettings @@ -557,6 +555,8 @@ object UpdateChecker { runnable.invoke() } else { + SettingsEntryPointAction.newPlatformUpdate(checkForUpdateResult, checkPluginsUpdateResult.incompatiblePlugins) + IdeUpdateUsageTriggerCollector.trigger("notification.shown") val title = IdeBundle.message("updates.new.build.notification.title", ApplicationNamesInfo.getInstance().fullProductName, newBuild.version) @@ -583,33 +583,32 @@ object UpdateChecker { } // don't show notification if all updated plugins is disabled else if (updatedPlugins.size != updatedPlugins.count { downloader -> PluginManagerCore.isDisabled(downloader.id) }) { + SettingsEntryPointAction.newPluginsUpdate(updatedPlugins, checkPluginsUpdateResult.customRepositoryPlugins) + val runnable = { PluginManagerConfigurable.showPluginConfigurable(project, updatedPlugins.map { it.descriptor }) } - val ideFrame = WelcomeFrame.getInstance() - if (ideFrame is WelcomeFrameUpdater) { - ideFrame.showPluginUpdates(runnable) - } - else { - val names = updatedPlugins.joinToString { downloader -> StringUtil.wrapWithDoubleQuote(downloader.pluginName) } - val title = if (updatedPlugins.size == 1) IdeBundle.message("updates.plugin.ready.short.title.available", names) else IdeBundle.message("updates.plugins.ready.short.title.available") - val message = if (updatedPlugins.size == 1) "" else names - showNotification(project, title, message, runnable, { notification -> - notification.actions[0].templatePresentation.text = IdeBundle.message("plugin.settings.link.title") - val text = if (updatedPlugins.size == 1) IdeBundle.message("plugins.configurable.update.button") else IdeBundle.message("plugin.manager.update.all") - notification.actions.add(0, object : NotificationAction(text) { - override fun actionPerformed(e: AnActionEvent, notification: Notification) { - notification.expire() - PluginUpdateDialog.runUpdateAll(updatedPlugins, e.getData(PlatformDataKeys.CONTEXT_COMPONENT) as JComponent?) - } - }) - notification.addAction(object : NotificationAction(IdeBundle.message("updates.ignore.updates.link", updatedPlugins.size)) { - override fun actionPerformed(e: AnActionEvent, notification: Notification) { - notification.expire() - PluginUpdateDialog.ignorePlugins(updatedPlugins.map { downloader -> downloader.descriptor }) - } - }) - }, NotificationUniqueType.PLUGINS, "plugins.update.available") - } + val names = updatedPlugins.joinToString { downloader -> StringUtil.wrapWithDoubleQuote(downloader.pluginName) } + val title = if (updatedPlugins.size == 1) IdeBundle.message("updates.plugin.ready.short.title.available", names) + else IdeBundle.message("updates.plugins.ready.short.title.available") + val message = if (updatedPlugins.size == 1) "" else names + + showNotification(project, title, message, runnable, { notification -> + notification.actions[0].templatePresentation.text = IdeBundle.message("plugin.settings.link.title") + val text = if (updatedPlugins.size == 1) IdeBundle.message("plugins.configurable.update.button") + else IdeBundle.message("plugin.manager.update.all") + notification.actions.add(0, object : NotificationAction(text) { + override fun actionPerformed(e: AnActionEvent, notification: Notification) { + notification.expire() + PluginUpdateDialog.runUpdateAll(updatedPlugins, e.getData(PlatformDataKeys.CONTEXT_COMPONENT) as JComponent?) + } + }) + notification.addAction(object : NotificationAction(IdeBundle.message("updates.ignore.updates.link", updatedPlugins.size)) { + override fun actionPerformed(e: AnActionEvent, notification: Notification) { + notification.expire() + PluginUpdateDialog.ignorePlugins(updatedPlugins.map { downloader -> downloader.descriptor }) + } + }) + }, NotificationUniqueType.PLUGINS, "plugins.update.available") } } @@ -647,9 +646,6 @@ object UpdateChecker { } private fun canEnableNotifications(): Boolean { - if (WelcomeFrame.getInstance() is WelcomeFrameUpdater) { - return true - } return NotificationsConfigurationImpl.getInstanceImpl().SHOW_BALLOONS && NotificationsConfigurationImpl.getSettings( getNotificationGroup().displayId).displayType != NotificationDisplayType.NONE } @@ -662,7 +658,8 @@ object UpdateChecker { notificationType: NotificationUniqueType, notificationDisplayId: String) { val content = if (message.isEmpty()) "" else XmlStringUtil.wrapInHtml(message) - val notification = getNotificationGroup().createNotification(title, content, NotificationType.INFORMATION, null, notificationDisplayId) + val type = if (notificationType == NotificationUniqueType.PLATFORM) NotificationType.IDE_UPDATE else NotificationType.INFORMATION + val notification = getNotificationGroup().createNotification(title, content, type, null, notificationDisplayId) notification.collapseActionsDirection = Notification.CollapseActionsDirection.KEEP_LEFTMOST notification.addAction(object : NotificationAction(IdeBundle.message("updates.notification.update.action")) { override fun actionPerformed(e: AnActionEvent, notification: Notification) { diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java index aa966a1490b8..dd65b0b97d65 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java @@ -20,6 +20,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; @@ -48,7 +49,7 @@ import static com.intellij.openapi.util.Pair.pair; /** * @author pti */ -final class UpdateInfoDialog extends AbstractUpdateDialog { +public final class UpdateInfoDialog extends AbstractUpdateDialog { private final UpdateChannel myUpdatedChannel; private final Collection myUpdatedPlugins; private final BuildInfo myNewBuild; @@ -58,7 +59,7 @@ final class UpdateInfoDialog extends AbstractUpdateDialog { private final File myTestPatch; private final AbstractAction myWhatsNewAction; - UpdateInfoDialog(@NotNull UpdateChannel channel, + public UpdateInfoDialog(@NotNull UpdateChannel channel, @NotNull BuildInfo newBuild, @Nullable UpdateChain patches, boolean enableLink, @@ -213,30 +214,46 @@ final class UpdateInfoDialog extends AbstractUpdateDialog { } private void downloadPatchAndRestart() { - boolean updatePlugins = !ContainerUtil.isEmpty(myUpdatedPlugins); - if (updatePlugins && !new PluginUpdateInfoDialog(myUpdatedPlugins).showAndGet()) { + if (!ContainerUtil.isEmpty(myUpdatedPlugins) && !new PluginUpdateInfoDialog(myUpdatedPlugins).showAndGet()) { return; // update cancelled } + downloadPatchAndRestart(myNewBuild, myUpdatedChannel, myPatches, myTestPatch, myUpdatedPlugins, null); + } + public static void downloadPatchAndRestart(@NotNull BuildInfo newBuild, + @NotNull UpdateChannel updatedChannel, + @NotNull UpdateChain patches, + @Nullable File testPatch, + @Nullable Collection updatedPlugins, + @Nullable ActionCallback callback) { new Task.Backgroundable(null, IdeBundle.message("update.preparing"), true, PerformInBackgroundOption.DEAF) { @Override public void run(@NotNull ProgressIndicator indicator) { String[] command; try { - if (myTestPatch != null) { - command = UpdateInstaller.preparePatchCommand(myTestPatch, indicator); + if (testPatch != null) { + command = UpdateInstaller.preparePatchCommand(testPatch, indicator); } else { - List files = UpdateInstaller.downloadPatchChain(myPatches.getChain(), indicator); + List files = UpdateInstaller.downloadPatchChain(patches.getChain(), indicator); command = UpdateInstaller.preparePatchCommand(files, indicator); } } - catch (ProcessCanceledException e) { throw e; } + catch (ProcessCanceledException e) { + if (callback != null) { + callback.setRejected(); + } + throw e; + } catch (Exception e) { Logger.getInstance(UpdateInstaller.class).warn(e); + if (callback != null) { + callback.setRejected(); + } + String title = IdeBundle.message("updates.notification.title", ApplicationNamesInfo.getInstance().getFullProductName()); - String downloadUrl = UpdateInfoPanel.downloadUrl(myNewBuild, myUpdatedChannel); + String downloadUrl = UpdateInfoPanel.downloadUrl(newBuild, updatedChannel); String message = IdeBundle.message("update.downloading.patch.error", e.getMessage(), downloadUrl); UpdateChecker.getNotificationGroup().createNotification( title, message, NotificationType.ERROR, NotificationListener.URL_OPENING_LISTENER, "ide.patch.download.failed").notify(null); @@ -244,8 +261,12 @@ final class UpdateInfoDialog extends AbstractUpdateDialog { return; } - if (updatePlugins) { - UpdateInstaller.installPluginUpdates(myUpdatedPlugins, indicator); + if (!ContainerUtil.isEmpty(updatedPlugins)) { + UpdateInstaller.installPluginUpdates(updatedPlugins, indicator); + } + + if (callback != null) { + callback.setDone(); } if (ApplicationManager.getApplication().isRestartCapable()) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java index 5e22bf98ef8b..82b2a0f7f282 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java @@ -37,7 +37,6 @@ import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBTextField; import com.intellij.ui.components.labels.ActionLink; -import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.ui.mac.TouchbarDataKeys; import com.intellij.ui.scale.JBUIScale; @@ -70,7 +69,7 @@ import java.util.List; /** * @author Konstantin Bulenkov */ -public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, AccessibleContextAccessor, WelcomeFrameUpdater { +public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, AccessibleContextAccessor { @SuppressWarnings("StaticNonFinalField") public static boolean USE_TABBED_WELCOME_SCREEN = Boolean.parseBoolean(SystemProperty.get("use.tabbed.welcome.screen", "true")); @@ -232,9 +231,8 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, Ac return pair.second; } - private final class FlatWelcomeScreen extends AbstractWelcomeScreen implements WelcomeFrameUpdater { + private final class FlatWelcomeScreen extends AbstractWelcomeScreen { private final DefaultActionGroup myTouchbarActions = new DefaultActionGroup(); - private LinkLabel myUpdatePluginsLink; private boolean inDnd; FlatWelcomeScreen() { @@ -352,14 +350,7 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, Ac ActionPanel actionPanel = createQuickStartActionPanel(); panel.add(actionPanel, BorderLayout.CENTER); myTouchbarActions.addAll(actionPanel.getActions()); - panel.add(createUpdatesSettingsAndDocs(), BorderLayout.SOUTH); - return panel; - } - - private JComponent createUpdatesSettingsAndDocs() { - JPanel panel = new NonOpaquePanel(new BorderLayout()); - panel.add(createUpdatePluginsLink(), BorderLayout.WEST); - panel.add(createSettingsAndDocsPanel(FlatWelcomeFrame.this), BorderLayout.EAST); + panel.add(createSettingsAndDocsPanel(FlatWelcomeFrame.this), BorderLayout.SOUTH); return panel; } @@ -469,27 +460,6 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, Ac } return null; } - - private JComponent createUpdatePluginsLink() { - myUpdatePluginsLink = new LinkLabel<>(IdeBundle.message("updates.plugins.welcome.screen.link.message"), null); - myUpdatePluginsLink.setVisible(false); - - NonOpaquePanel wrap = new NonOpaquePanel(myUpdatePluginsLink); - wrap.setBorder(JBUI.Borders.empty(0, 10, 8, 11)); - return wrap; - } - - @Override - public void showPluginUpdates(@NotNull Runnable callback) { - myUpdatePluginsLink.setListener((__, ___) -> callback.run(), null); - myUpdatePluginsLink.setVisible(true); - } - - @Override - public void hidePluginUpdates() { - myUpdatePluginsLink.setListener(null, null); - myUpdatePluginsLink.setVisible(false); - } } protected void extendActionsGroup(JPanel panel) { @@ -498,20 +468,6 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, Disposable, Ac protected void onFirstActionShown(@NotNull Component action) { } - @Override - public void showPluginUpdates(@NotNull Runnable callback) { - if (myScreen instanceof WelcomeFrameUpdater) { - ((WelcomeFrameUpdater)myScreen).showPluginUpdates(callback); - } - } - - @Override - public void hidePluginUpdates() { - if (myScreen instanceof WelcomeFrameUpdater) { - ((WelcomeFrameUpdater)myScreen).hidePluginUpdates(); - } - } - @Nullable @Override public BalloonLayout getBalloonLayout() { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java index b3b0b8470daa..957fee1266f1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java @@ -2,11 +2,14 @@ package com.intellij.openapi.wm.impl.welcomeScreen; import com.intellij.ide.IdeBundle; +import com.intellij.ide.plugins.CountComponent; import com.intellij.ide.plugins.InstalledPluginsState; import com.intellij.ide.plugins.PluginManagerConfigurable; +import com.intellij.ide.plugins.newui.PluginUpdatesService; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.util.NlsSafe; import com.intellij.openapi.wm.WelcomeScreenTab; import com.intellij.openapi.wm.WelcomeTabFactory; import com.intellij.ui.AncestorListenerAdapter; @@ -14,11 +17,13 @@ import com.intellij.ui.JBColor; import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UI; +import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.components.BorderLayoutPanel; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.AncestorEvent; +import java.awt.*; public class PluginsTabFactory implements WelcomeTabFactory { private static final Logger LOG = Logger.getInstance(PluginsTabFactory.class); @@ -26,6 +31,42 @@ public class PluginsTabFactory implements WelcomeTabFactory { @Override public @NotNull WelcomeScreenTab createWelcomeTab(@NotNull Disposable parentDisposable) { return new TabbedWelcomeScreen.DefaultWelcomeScreenTab(IdeBundle.message("welcome.screen.plugins.title"), WelcomeScreenEventCollector.TabType.TabNavPlugins) { + private PluginUpdatesService myService; + private final CountComponent myCountLabel = new CountComponent(); + private JList myList; + + { + myKeyComponent.setBorder(JBUI.Borders.empty(8, 0, 8, 8)); + myKeyComponent.add(myCountLabel, BorderLayout.EAST); + myCountLabel.setVisible(false); + + UIUtil.invokeLaterIfNeeded(() -> { + myService = PluginUpdatesService.connectWithCounter(countValue -> { + @NlsSafe String text = countValue == null || countValue <= 0 ? null : countValue.toString(); + myCountLabel.setText(text); + myCountLabel.setVisible(text != null); + if (myList != null) { + myList.repaint(); + } + }); + }); + } + + @Override + public @NotNull JComponent getKeyComponent(@NotNull JList list) { + if (myList == null) { + list.addAncestorListener(new AncestorListenerAdapter() { + @Override + public void ancestorRemoved(AncestorEvent event) { + if (myService != null) { + myService.dispose(); + } + } + }); + } + myList = list; + return super.getKeyComponent(list); + } @Override protected JComponent buildComponent() { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/TabbedWelcomeScreen.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/TabbedWelcomeScreen.java index 96a17222877b..fa1c7861af53 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/TabbedWelcomeScreen.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/TabbedWelcomeScreen.java @@ -131,7 +131,7 @@ public final class TabbedWelcomeScreen extends AbstractWelcomeScreen { int index, boolean isSelected, boolean cellHasFocus) { - JComponent keyComponent = value.getKeyComponent(); + JComponent keyComponent = value.getKeyComponent(list); JPanel wrappedPanel = JBUI.Panels.simplePanel(keyComponent); UIUtil.setBackgroundRecursively(wrappedPanel, isSelected ? UIUtil.getListSelectionBackground(cellHasFocus): getMainTabListBackground()); UIUtil.setForegroundRecursively(wrappedPanel, UIUtil.getListForeground(isSelected, cellHasFocus)); @@ -143,8 +143,7 @@ public final class TabbedWelcomeScreen extends AbstractWelcomeScreen { } public abstract static class DefaultWelcomeScreenTab implements WelcomeScreenTab, Accessible { - - private final JComponent myKeyComponent; + protected final JComponent myKeyComponent; private JComponent myAssociatedComponent; private final JBLabel myLabel; private final WelcomeScreenEventCollector.TabType myType; @@ -162,7 +161,7 @@ public final class TabbedWelcomeScreen extends AbstractWelcomeScreen { @Override @NotNull - public JComponent getKeyComponent() { + public JComponent getKeyComponent(@NotNull JList list) { return myKeyComponent; } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeFrameUpdater.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeFrameUpdater.java deleted file mode 100644 index 79d6923665c5..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeFrameUpdater.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.openapi.wm.impl.welcomeScreen; - -import org.jetbrains.annotations.NotNull; - -/** - * @author Alexander Lobas - */ -public interface WelcomeFrameUpdater { - void showPluginUpdates(@NotNull Runnable callback); - - void hidePluginUpdates(); -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeScreenComponentFactory.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeScreenComponentFactory.java index efad536aca9a..b76869fe5992 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeScreenComponentFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeScreenComponentFactory.java @@ -307,7 +307,9 @@ public final class WelcomeScreenComponentFactory { Topics.subscribe(WelcomeBalloonLayoutImpl.BALLOON_NOTIFICATION_TOPIC, parentDisposable, types -> { if (!types.isEmpty()) { NotificationType type = Collections.max(types); - actionLink.setIcon(IdeNotificationArea.createIconWithNotificationCount(panel, type, types.size(), false)); + actionLink.setIcon(type == NotificationType.IDE_UPDATE + ? AllIcons.Ide.Notification.IdeUpdate + : IdeNotificationArea.createIconWithNotificationCount(panel, type, types.size(), false)); } panel.setVisible(!types.isEmpty()); }); diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 9d2df1d8728c..2aa45e9532c8 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -1171,6 +1171,7 @@ + diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 456d86f10fc2..9311eefed155 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -501,8 +501,8 @@ - + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 81e270f71e65..c0dfc7b9a7db 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -165,6 +165,7 @@ + @@ -808,12 +809,10 @@ - - - + - + @@ -1353,5 +1352,16 @@ + + + + + + + + + + + diff --git a/platform/util/src/com/intellij/icons/AllIcons.java b/platform/util/src/com/intellij/icons/AllIcons.java index c3ce37dec400..96bcac5ecc86 100644 --- a/platform/util/src/com/intellij/icons/AllIcons.java +++ b/platform/util/src/com/intellij/icons/AllIcons.java @@ -584,8 +584,10 @@ public class AllIcons { /** 16x16 */ public static final @NotNull Icon ExpandHover = load("ide/notification/expandHover.svg", -6923529455314709892L, 0); /** 16x16 */ public static final @NotNull Icon Gear = load("ide/notification/gear.svg", -6517441912232259723L, 0); /** 16x16 */ public static final @NotNull Icon GearHover = load("ide/notification/gearHover.svg", 8529034088585467841L, 0); + /** 16x16 */ public static final @NotNull Icon IdeUpdate = load("ide/notification/ideUpdate.svg", -5687507774382455241L, 2); /** 16x16 */ public static final @NotNull Icon InfoEvents = load("ide/notification/infoEvents.svg", 8320605274562923160L, 2); /** 16x16 */ public static final @NotNull Icon NoEvents = load("ide/notification/noEvents.svg", -5934625381114731420L, 2); + /** 16x16 */ public static final @NotNull Icon PluginUpdate = load("ide/notification/pluginUpdate.svg", 7670193732028920558L, 2); /** 16x16 */ public static final @NotNull Icon WarningEvents = load("ide/notification/warningEvents.svg", -7338122458505861009L, 2); } diff --git a/platform/vcs-impl/resources/META-INF/VcsActions.xml b/platform/vcs-impl/resources/META-INF/VcsActions.xml index 1deb56f4f778..2ed3c37aaae5 100644 --- a/platform/vcs-impl/resources/META-INF/VcsActions.xml +++ b/platform/vcs-impl/resources/META-INF/VcsActions.xml @@ -341,7 +341,7 @@ - + diff --git a/resources/src/idea/JavaActions.xml b/resources/src/idea/JavaActions.xml index 723831046ffb..7d279df43871 100644 --- a/resources/src/idea/JavaActions.xml +++ b/resources/src/idea/JavaActions.xml @@ -37,8 +37,7 @@ - - +