IDEA-252419 Quick Access Menu to Themes, Plugins, Keymaps and IDE/Plugin updates
authorAlexander Lobas <Alexander.Lobas@jetbrains.com>
Mon, 2 Nov 2020 18:16:26 +0000 (21:16 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Mon, 2 Nov 2020 18:17:34 +0000 (18:17 +0000)
GitOrigin-RevId: fc66de1f90999e421870545817cadc33c27214fc

31 files changed:
platform/icons/src/ide/notification/ideUpdate.svg [new file with mode: 0644]
platform/icons/src/ide/notification/ideUpdate_dark.svg [new file with mode: 0644]
platform/icons/src/ide/notification/pluginUpdate.svg [new file with mode: 0644]
platform/icons/src/ide/notification/pluginUpdate_dark.svg [new file with mode: 0644]
platform/platform-api/resources/messages/IdeBundle.properties
platform/platform-api/src/com/intellij/notification/NotificationType.java
platform/platform-api/src/com/intellij/openapi/wm/WelcomeScreenTab.java
platform/platform-impl/src/com/intellij/ide/actions/QuickChangeKeymapAction.java
platform/platform-impl/src/com/intellij/ide/actions/QuickChangeLookAndFeel.java
platform/platform-impl/src/com/intellij/ide/actions/SettingsEntryPointAction.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/ide/actions/ShowPluginsWithSearchOptionAction.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java
platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableTreeRenderer.java
platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginUpdatesService.java
platform/platform-impl/src/com/intellij/notification/EventLogConsole.java
platform/platform-impl/src/com/intellij/notification/impl/NotificationCollector.java
platform/platform-impl/src/com/intellij/notification/impl/widget/IdeNotificationArea.java
platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginUpdateDialog.java
platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.kt
platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/TabbedWelcomeScreen.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeFrameUpdater.java [deleted file]
platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeScreenComponentFactory.java
platform/platform-resources/src/META-INF/LangExtensions.xml
platform/platform-resources/src/idea/LangActions.xml
platform/platform-resources/src/idea/PlatformActions.xml
platform/util/src/com/intellij/icons/AllIcons.java
platform/vcs-impl/resources/META-INF/VcsActions.xml
resources/src/idea/JavaActions.xml

diff --git a/platform/icons/src/ide/notification/ideUpdate.svg b/platform/icons/src/ide/notification/ideUpdate.svg
new file mode 100644 (file)
index 0000000..d89e9fa
--- /dev/null
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect width="16" height="16" rx="8" fill="#EDA200"/>
+  <rect x="8.80005" y="4.8" width="7.2" height="1.6" transform="rotate(90 8.80005 4.8)" fill="white"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M8 4L12 8L4 8L8 4Z" fill="white"/>
+</svg>
\ 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 (file)
index 0000000..12ee6b2
--- /dev/null
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect width="16" height="16" rx="8" fill="#F0A732"/>
+  <rect x="8.80005" y="4.8" width="7.2" height="1.6" transform="rotate(90 8.80005 4.8)" fill="white"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M8 4L12 8L4 8L8 4Z" fill="white"/>
+</svg>
\ 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 (file)
index 0000000..3ea25b9
--- /dev/null
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect width="16" height="16" rx="8" fill="#389FD6"/>
+  <rect x="8.80005" y="4.8" width="7.2" height="1.6" transform="rotate(90 8.80005 4.8)" fill="white"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M8 4L12 8L4 8L8 4Z" fill="white"/>
+</svg>
\ 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 (file)
index 0000000..35033e9
--- /dev/null
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <rect width="16" height="16" rx="8" fill="#3592C4"/>
+  <rect x="8.80005" y="4.8" width="7.2" height="1.6" transform="rotate(90 8.80005 4.8)" fill="white"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M8 4L12 8L4 8L8 4Z" fill="white"/>
+</svg>
\ No newline at end of file
index be53f290fc2061f5e65a49660b62dc0b759619f2..c680ed943b9bde6c98a20ff282139f3816609300 100644 (file)
@@ -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.
index 1981ad82900025fc627230f4dcde1d4fd132f67e..23edef107b5517f7416818456bdd26cef168b30c 100644 (file)
@@ -19,6 +19,7 @@ package com.intellij.notification;
  * @author spleaner
  */
 public enum NotificationType {
+  IDE_UPDATE,
   INFORMATION,
   WARNING,
   ERROR
index a9f3dc474997cc2eb8105541570c64a2997826c6..acc99f0ecd3c18e698dc108e3f0debb30ad2181d 100644 (file)
@@ -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<? extends WelcomeScreenTab> list);
 
   /**
    * @return component shown when related key component is selected
index c022c44de7755cd608104f71239307096b585cef..95c680f86ccad7eb97551eed7362305d9b893910 100644 (file)
@@ -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
index 273af2380149097fcff289f23df639cd8bada1cc..535c27591b3025a0943bccb8e813283d7cd5b071 100644 (file)
@@ -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 (file)
index 0000000..6387b58
--- /dev/null
@@ -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<PluginDownloader> 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<PluginDownloader> 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<IdeaPluginDescriptor> myIncompatiblePlugins;
+
+  private static Collection<PluginDownloader> myUpdatedPlugins;
+  private static Collection<IdeaPluginDescriptor> myCustomRepositoryPlugins;
+
+  public static void newPlatformUpdate(@Nullable CheckForUpdateResult platformUpdateInfo,
+                                       @Nullable Collection<IdeaPluginDescriptor> incompatiblePlugins) {
+    myPlatformUpdateInfo = platformUpdateInfo;
+    myIncompatiblePlugins = incompatiblePlugins;
+    updateAction();
+  }
+
+  public static void newPluginsUpdate(@Nullable Collection<PluginDownloader> updatedPlugins,
+                                      @Nullable Collection<IdeaPluginDescriptor> 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<MouseEvent> 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 (file)
index 0000000..fb789f8
--- /dev/null
@@ -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
index 0f99ee9b700c7e3a2b9209f4ffc2e1845bef20cc..599d6fa642dde8924e75aa5c185aa2c42e9ed841 100644 (file)
@@ -132,6 +132,8 @@ public class PluginManagerConfigurable
 
   private Collection<IdeaPluginDescriptor> 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
index bcdae572d331bb45f5e0a9477445883adf77a565..ce44010a3ab184a60270d57c5b7c43d3b25d0b06 100644 (file)
@@ -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() {
index f2fb995a4098ff95445ed9c609dd58e5b9958e64..dbe004340d52b82140ec6233bac903ff7d4a869d 100644 (file)
@@ -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<PluginUpdatesService> SERVICES = new ArrayList<>();
+  private static final Object ourLock = new Object();
   private static Collection<IdeaPluginDescriptor> myCache;
   private static boolean myPrepared;
   private static boolean myPreparing;
   private static boolean myReset;
 
-  private Consumer<? super Integer> myTreeCallback;
-  private Consumer<? super Integer> myTabCallback;
-  private Consumer<? super Collection<IdeaPluginDescriptor>> myInstalledPanelCallback;
-  private Consumer<? super Collection<IdeaPluginDescriptor>> myUpdatePanelCallback;
+  private Consumer<? super Integer> myCountCallback;
+  private Consumer<? super Collection<IdeaPluginDescriptor>> myUpdateCallback;
 
   @NotNull
-  public static PluginUpdatesService connectTreeRenderer(@NotNull Consumer<? super Integer> callback) {
-    checkAccess();
-
+  public static PluginUpdatesService connectWithCounter(@NotNull Consumer<? super Integer> 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<? super Integer> callback) {
-    checkAccess();
-
+  public static PluginUpdatesService connectWithUpdates(@NotNull Consumer<? super Collection<IdeaPluginDescriptor>> 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<? super Collection<IdeaPluginDescriptor>> callback) {
-    checkAccess();
-    myInstalledPanelCallback = callback;
-
-    if (myPrepared) {
-      callback.accept(myCache);
-    }
-    else {
-      calculateUpdates();
-    }
-  }
-
   public void calculateUpdates(@NotNull Consumer<? super Collection<IdeaPluginDescriptor>> 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();
+    synchronized (ourLock) {
+      if (!myPrepared || myCache == null) {
+        return;
+      }
 
-    if (!myPrepared || myCache == null) {
-      return;
-    }
+      for (Iterator<IdeaPluginDescriptor> I = myCache.iterator(); I.hasNext(); ) {
+        IdeaPluginDescriptor downloadedDescriptor = I.next();
 
-    for (Iterator<IdeaPluginDescriptor> I = myCache.iterator(); I.hasNext(); ) {
-      IdeaPluginDescriptor downloadedDescriptor = I.next();
+        if (downloadedDescriptor.equals(descriptor)) {
+          I.remove();
 
-      if (downloadedDescriptor.equals(descriptor)) {
-        I.remove();
+          Integer countValue = getCount();
+          for (PluginUpdatesService service : SERVICES) {
+            service.runCountCallbacks(countValue);
+          }
 
-        Integer countValue = getCount();
-        for (PluginUpdatesService service : SERVICES) {
-          service.runCountCallbacks(countValue);
+          return;
         }
-
-        return;
       }
     }
   }
 
   public void finishUpdate() {
-    checkAccess();
-
-    if (!myPrepared || myCache == null) {
-      return;
-    }
+    synchronized (ourLock) {
+      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();
-
-    for (PluginUpdatesService service : SERVICES) {
-      service.runAllCallbacks(0);
-    }
+    synchronized (ourLock) {
+      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<IdeaPluginDescriptor> 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();
-
-        myPreparing = false;
-
-        if (myReset) {
-          myReset = false;
-          calculateUpdates();
-          return;
-        }
-
-        myPrepared = true;
-        List<IdeaPluginDescriptor> cache = new ArrayList<>();
-        Collection<PluginDownloader> 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);
+        synchronized (ourLock) {
+          myPreparing = false;
+
+          if (myReset) {
+            myReset = false;
+            calculateUpdates();
+            return;
+          }
+
+          myPrepared = true;
+          List<IdeaPluginDescriptor> cache = new ArrayList<>();
+          Collection<PluginDownloader> 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);
+          }
         }
       }, 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
index 69d82a24aee138208ad72bf5bcc88e62da408dc2..4edc7b17fc55a05be946d64f0384e8f18f4e3237 100644 (file)
@@ -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;
 
index ed962128dd355f27175d3ac64c1f0b40613ae195..d25389ceeb4e23383aed982e40a21eecfeaa9b6f 100644 (file)
@@ -62,7 +62,7 @@ public final class NotificationCollector {
                               boolean isExpandable) {
     List<EventPair<?>> 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<EventPair<?>> 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<EventPair<?>> 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) {
index e12a720e35f3ad431818d8f4bd56381e3de7d560..a0d5e37ffc8ae602168a2f62ae02ee5b7b661639 100644 (file)
@@ -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<? extends Notification> 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;
       }
     }
index fb1dba1d3b15381b969ce65d94e0f71879c145c1..6679012efc7ee80cc41929b2deb681b6ebad7276 100644 (file)
@@ -59,7 +59,7 @@ public class PluginUpdateDialog extends DialogWrapper {
 
   public PluginUpdateDialog(@Nullable Project project,
                             @NotNull Collection<PluginDownloader> updatedPlugins,
-                            @NotNull Collection<IdeaPluginDescriptor> customRepositoryPlugins) {
+                            @Nullable Collection<IdeaPluginDescriptor> customRepositoryPlugins) {
     super(true);
     setTitle(IdeBundle.message("dialog.title.plugin.updates"));
 
@@ -82,7 +82,7 @@ public class PluginUpdateDialog extends DialogWrapper {
       @Override
       @NotNull
       protected Collection<IdeaPluginDescriptor> getCustomRepoPlugins() {
-        return customRepositoryPlugins;
+        return customRepositoryPlugins == null ? super.getCustomRepoPlugins() : customRepositoryPlugins;
       }
     };
 
index 71c3b666099a10afbfc1924475fd8a539eac606a..1635ef35802c8534b7ac547b610d0a69a481159a 100644 (file)
@@ -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) {
index aa966a1490b8e0d5355840f7db75203558bbbbfa..dd65b0b97d65e20c5589886619154c92af8509e5 100644 (file)
@@ -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<PluginDownloader> 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<PluginDownloader> 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<File> files = UpdateInstaller.downloadPatchChain(myPatches.getChain(), indicator);
+            List<File> 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()) {
index 5e22bf98ef8bc85a8e0fb114e02e77acb87d2ccc..82b2a0f7f28259c54ad80b59f5d909bcc13ef9ae 100644 (file)
@@ -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<Object> 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() {
index b3b0b8470daa4af196b0ccc085a338e6627b2747..957fee1266f14455a8aa7a19bfea773be73779fc 100644 (file)
@@ -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<? extends WelcomeScreenTab> 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<? extends WelcomeScreenTab> 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() {
index 96a17222877b21c0cfdef4c2cb9c962cf3c7b1a0..fa1c7861af53802df717ff2dcf19a97c5d050bfd 100644 (file)
@@ -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<? extends WelcomeScreenTab> 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 (file)
index 79d6923..0000000
+++ /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
index efad536aca9afce4c15a12a91ca58c4d194b563b..b76869fe59920cdb11b2451de548aa42f6c6d91c 100644 (file)
@@ -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());
     });
index 9d2df1d8728c668bf9cf1544d6ae699d0a449cd1..2aa45e9532c845979ffb355c37733c055f7bb9cd 100644 (file)
     <statusBarWidgetFactory id="fatalErrorWidget" implementation="com.intellij.openapi.wm.impl.status.FatalErrorWidgetFactory" order="after notificationsWidget"/>
     <statusBarWidgetFactory id="writeActionWidget" implementation="com.intellij.openapi.wm.impl.status.WriteThreadIndicatorWidgetFactory" order="after fatalErrorWidget"/>
     <statusBarWidgetFactory id="memoryUsageWidget" implementation="com.intellij.openapi.wm.impl.status.MemoryIndicatorWidgetFactory" order="last"/>
+    <statusBarWidgetFactory id="settingsEntryPointWidget" implementation="com.intellij.ide.actions.SettingsEntryPointAction$StatusBarManager" order="last"/>
 
     <fileIndentOptionsProvider implementation="com.intellij.psi.codeStyle.DetectableIndentOptionsProvider" order="last"/>
     <lang.formatter.restriction implementation="com.intellij.formatting.ExcludedFileFormattingRestriction"/>
index 456d86f10fc2a406b28d006dc00cfea7eea4d3ef..9311eefed1555a1453377e23e9cb3b9e1148f69d 100644 (file)
       <separator/>
       <reference id="NavBarToolBarOthers"/>
       <separator/>
-      <reference ref="RunAnything"/>
       <reference ref="SearchEverywhere"/>
+      <reference ref="SettingsEntryPoint"/>
     </group>
 
 
index 81e270f71e658293f4bc8ec4021f4ad803ac04da..c0dfc7b9a7db8cdfc75750f243c04886eeffa584 100644 (file)
     </action>
     <action id="OpenElementInNewWindow" class="com.intellij.ide.actions.OpenElementInNewWindowAction" />
     <action id="SearchEverywhere" class="com.intellij.ide.actions.SearchEverywhereAction" icon="AllIcons.Actions.Find"/>
+    <action id="SettingsEntryPoint" class="com.intellij.ide.actions.SettingsEntryPointAction" icon="AllIcons.General.GearPlain"/>
 
     <action id="RunAnything" icon="AllIcons.Actions.Run_anything" class="com.intellij.ide.actions.runAnything.RunAnythingAction"/>
 
       <reference ref="Back"/>
       <reference ref="Forward"/>
       <separator/>
-      <group id="MainToolBarSettings">
-        <reference ref="ShowSettings"/>
-      </group>
+      <group id="MainToolBarSettings"/>
       <separator/>
-      <reference ref="RunAnything"/>
       <reference ref="SearchEverywhere"/>
+      <reference ref="SettingsEntryPoint"/>
     </group>
 
     <group id="NavBarVcsGroup"/>
     </group>
 
     <action class="com.intellij.diagnostic.ResetWindowsDefenderNotification" id="ResetWindowsDefenderNotification"/>
+
+    <group id="SettingsEntryPointGroup">
+      <reference id="RunAnything"/>
+      <separator/>
+      <reference id="ShowSettings"/>
+      <reference id="WelcomeScreen.Plugins"/>
+      <separator/>
+      <reference id="ChangeLaf"/>
+      <reference id="ChangeKeymap"/>
+      <reference id="ChangeView"/>
+    </group>
   </actions>
 </idea-plugin>
index c3ce37dec400dde28c52e1206de014248a6c0c6c..96bcac5ecc86a122d88d4c252e7c8a1cdf372487 100644 (file)
@@ -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);
     }
 
index 1deb56f4f778c23d0f1e60d12272ef9230c97620..2ed3c37aaae5bc430dfe7a000adbeefa27a1dc85 100644 (file)
       <reference ref="Vcs.ShowTabbedFileHistory"/>
       <reference ref="ChangesView.Revert"/>
       <separator/>
-      <add-to-group group-id="MainToolBarSettings" relative-to-action="ShowSettings" anchor="before"/>
+      <add-to-group group-id="MainToolBarSettings"/>
     </group>
 
     <group id="VcsNavBarToolbarActions">
index 723831046ffb972ff572594b4bfa4c2d5728f978..7d279df43871fa4e0f849668e89878834b79c570 100644 (file)
@@ -37,8 +37,7 @@
     <action id="ShowProjectStructureSettings" class="com.intellij.ide.actions.ShowStructureSettingsAction"
             icon="AllIcons.General.ProjectStructure">
       <add-to-group group-id="FileMainSettingsGroup" anchor="after" relative-to-action="ShowSettings"/>
-      <add-to-group group-id="MainToolBarSettings" anchor="after" relative-to-action="ShowSettings"/>
-      <add-to-group group-id="NavBarToolBar" relative-to-action="NavBarToolBarOthers" anchor="after"/>
+      <add-to-group group-id="SettingsEntryPointGroup" anchor="before" relative-to-action="ShowSettings"/>
     </action>
     <action id="TemplateProjectStructure" class="com.intellij.ide.actions.TemplateProjectStructureAction">
       <add-to-group group-id="FileSettingsGroup" anchor="last"/>