diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/LinkPanel.java b/platform/platform-impl/src/com/intellij/ide/plugins/LinkPanel.java index 4e872648eb99..e584e5e2288c 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/LinkPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/LinkPanel.java @@ -3,7 +3,6 @@ package com.intellij.ide.plugins; import com.intellij.icons.AllIcons; import com.intellij.ide.plugins.newui.CellPluginComponent; -import com.intellij.ide.plugins.newui.TextHorizontalLayout; import com.intellij.ui.components.labels.LinkLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,18 +14,19 @@ import javax.swing.*; */ public class LinkPanel { private final JLabel myTextLabel = new JLabel(); - private final JLabel myLinkLabel = createLink(); + private final JLabel myLinkLabel; private Runnable myRunnable; - public LinkPanel(@NotNull JPanel parent) { + public LinkPanel(@NotNull JPanel parent, boolean icon, @Nullable Object labelConstraints, @Nullable Object linkConstraints) { + myLinkLabel = createLink(icon); myTextLabel.setForeground(CellPluginComponent.GRAY_COLOR); - parent.add(myTextLabel, TextHorizontalLayout.FIX_LABEL); - parent.add(myLinkLabel); + parent.add(myTextLabel, labelConstraints); + parent.add(myLinkLabel, linkConstraints); } @NotNull - private JLabel createLink() { - LinkLabel linkLabel = new LinkLabel<>(null, AllIcons.Ide.External_link_arrow, (__, ___) -> myRunnable.run()); + private JLabel createLink(boolean icon) { + LinkLabel linkLabel = new LinkLabel<>(null, icon ? AllIcons.Ide.External_link_arrow : null, (__, ___) -> myRunnable.run()); linkLabel.setIconTextGap(0); linkLabel.setHorizontalTextPosition(SwingConstants.LEFT); return linkLabel; diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableNewLayout.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableNewLayout.java index d8b8d6acae65..51a1bb840b11 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableNewLayout.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurableNewLayout.java @@ -2,12 +2,12 @@ package com.intellij.ide.plugins; import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.icons.AllIcons; +import com.intellij.ide.DataManager; import com.intellij.ide.IdeBundle; import com.intellij.ide.plugins.newui.*; import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ex.ApplicationInfoEx; @@ -17,25 +17,37 @@ import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupListener; +import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.updateSettings.impl.PluginDownloader; import com.intellij.openapi.updateSettings.impl.UpdateSettings; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ThrowableNotNullFunction; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.JBColor; +import com.intellij.ui.SeparatorWithText; import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.JBTextField; +import com.intellij.ui.components.fields.ExtendableTextComponent; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.components.labels.LinkListener; +import com.intellij.ui.popup.PopupFactoryImpl; +import com.intellij.ui.popup.list.PopupListElementRenderer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.StatusText; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; import java.awt.*; import java.io.IOException; import java.util.List; @@ -88,7 +100,16 @@ public class PluginManagerConfigurableNewLayout private Map myAllRepositoriesMap; private Map> myCustomRepositoriesMap; private final Object myRepositoriesLock = new Object(); - private List myAllTagSorted; + private List myTagsSorted; + private List myVendorsSorted; + + private DefaultActionGroup myMarketplaceSortByGroup; + private Consumer myMarketplaceSortByCallback; + private LinkComponent myMarketplaceSortByAction; + + private DefaultActionGroup myInstalledSearchGroup; + private Runnable myInstalledSearchCallback; + private boolean myInstalledSearchSetState = true; @NotNull @Override @@ -215,6 +236,50 @@ public class PluginManagerConfigurableNewLayout return actions; } + private static void showRightBottomPopup(@NotNull Component component, @NotNull String title, @NotNull ActionGroup group) { + DefaultActionGroup actions = new GroupByActionGroup(); + actions.addSeparator(" " + title); + actions.addAll(group); + + DataContext context = DataManager.getInstance().getDataContext(component); + + JBPopup popup = new PopupFactoryImpl.ActionGroupPopup(null, actions, context, false, false, false, true, null, -1, null, null) { + @Override + protected ListCellRenderer getListElementRenderer() { + return new PopupListElementRenderer(this) { + @Override + protected SeparatorWithText createSeparator() { + return new SeparatorWithText() { + { + setTextForeground(JBColor.BLACK); + setFont(UIUtil.getLabelFont()); + setCaptionCentered(false); + } + + @Override + protected void paintLine(Graphics g, int x, int y, int width) { + } + }; + } + + @Override + protected Border getDefaultItemComponentBorder() { + return new EmptyBorder(JBUI.insets(UIUtil.getListCellVPadding(), 15)); + } + }; + } + }; + 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 + component.getHeight())); + } + }); + popup.show(component); + } + private void resetPanels() { synchronized (myRepositoriesLock) { myAllRepositoriesList = null; @@ -222,6 +287,9 @@ public class PluginManagerConfigurableNewLayout myCustomRepositoriesMap = null; } + myTagsSorted = null; + myVendorsSorted = null; + myPluginUpdatesService.recalculateUpdates(); if (myMarketplacePanel == null) { @@ -345,11 +413,12 @@ public class PluginManagerConfigurableNewLayout @Override protected List getAttributes() { List attributes = new ArrayList<>(); - attributes.add("tag:"); + attributes.add("/tag:"); + attributes.add("/sortBy:"); + attributes.add("/vendor:"); if (!UpdateSettings.getInstance().getPluginHosts().isEmpty()) { - attributes.add("repository:"); + attributes.add("/repository:"); } - attributes.add("sortBy:"); return attributes; } @@ -357,8 +426,8 @@ public class PluginManagerConfigurableNewLayout @Override protected List getValues(@NotNull String attribute) { switch (attribute) { - case "tag:": - if (ContainerUtil.isEmpty(myAllTagSorted)) { + case "/tag:": + if (ContainerUtil.isEmpty(myTagsSorted)) { Set allTags = new HashSet<>(); for (IdeaPluginDescriptor descriptor : getPluginRepositories()) { if (descriptor instanceof PluginNode) { @@ -368,13 +437,25 @@ public class PluginManagerConfigurableNewLayout } } } - myAllTagSorted = ContainerUtil.sorted(allTags, String::compareToIgnoreCase); + myTagsSorted = ContainerUtil.sorted(allTags, String::compareToIgnoreCase); } - return myAllTagSorted; - case "repository:": - return UpdateSettings.getInstance().getPluginHosts(); - case "sortBy:": + return myTagsSorted; + case "/sortBy:": return ContainerUtil.list("downloads", "name", "rating", "featured", "updated"); + case "/vendor:": + if (ContainerUtil.isEmpty(myVendorsSorted)) { + Set vendors = new HashSet<>(); + for (IdeaPluginDescriptor descriptor : getPluginRepositories()) { + String vendor = StringUtil.trim(descriptor.getVendor()); + if (!StringUtil.isEmptyOrSpaces(vendor)) { + vendors.add(vendor); + } + } + myVendorsSorted = ContainerUtil.sorted(vendors, String::compareToIgnoreCase); + } + return myVendorsSorted; + case "/repository:": + return UpdateSettings.getInstance().getPluginHosts(); } return null; } @@ -403,6 +484,98 @@ public class PluginManagerConfigurableNewLayout } }; + myMarketplaceSortByGroup = new DefaultActionGroup(); + + for (SortBySearchOption option : SortBySearchOption.values()) { + myMarketplaceSortByGroup.addAction(new MarketplaceSortByAction(option)); + } + + myMarketplaceSortByAction = new LinkComponent() { + @Override + protected boolean isInClickableArea(Point pt) { + return true; + } + }; + myMarketplaceSortByAction.setIcon(new Icon() { + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + getIcon().paintIcon(c, g, x, y + 1); + } + + @Override + public int getIconWidth() { + return getIcon().getIconWidth(); + } + + @Override + public int getIconHeight() { + return getIcon().getIconHeight(); + } + + @NotNull + private Icon getIcon() { + return AllIcons.General.ButtonDropTriangle; + } + }); // TODO: icon + myMarketplaceSortByAction.setPaintUnderline(false); + myMarketplaceSortByAction.setIconTextGap(JBUI.scale(4)); + myMarketplaceSortByAction.setHorizontalTextPosition(SwingConstants.LEFT); + myMarketplaceSortByAction.setForeground(PluginsGroupComponent.SECTION_HEADER_FOREGROUND); + + //noinspection unchecked + myMarketplaceSortByAction.setListener( + (component, __) -> showRightBottomPopup(component.getParent().getParent(), "Sort By", myMarketplaceSortByGroup), null); + + myMarketplaceSortByCallback = updateAction -> { + MarketplaceSortByAction removeAction = null; + MarketplaceSortByAction addAction = null; + + if (updateAction.myState) { + for (AnAction action : myMarketplaceSortByGroup.getChildren(null)) { + MarketplaceSortByAction sortByAction = (MarketplaceSortByAction)action; + if (sortByAction != updateAction && sortByAction.myState) { + sortByAction.myState = false; + removeAction = sortByAction; + break; + } + } + addAction = updateAction; + } + else { + removeAction = updateAction; + } + + List queries = new ArrayList<>(); + new SearchQueryParser.Marketplace(searchTextField.getText()) { + @Override + protected void setSearchQuery(@NotNull String query) { + super.setSearchQuery(query); + queries.add(query); + } + + @Override + protected void handleAttribute(@NotNull String name, @NotNull String value, boolean invert) { + super.handleAttribute(name, value, invert); + queries.add(name + ":" + value); + } + }; + if (removeAction != null) { + queries.remove(removeAction.getQuery()); + } + if (addAction != null) { + queries.add(addAction.getQuery()); + } + + String query = StringUtil.join(queries, " "); + searchTextField.setTextIgnoreEvents(query); + if (query.isEmpty()) { + myMarketplaceTab.hideSearchPanel(); + } + else { + myMarketplaceTab.showSearchPanel(query); + } + }; + PluginsGroupComponentWithProgress panel = new PluginsGroupComponentWithProgress(new PluginListLayout(), new MultiSelectionEventHandler(), myNameListener, PluginManagerConfigurableNewLayout.this.mySearchListener, @@ -420,7 +593,18 @@ public class PluginManagerConfigurableNewLayout Map allRepositoriesMap = p.first; Map> customRepositoriesMap = p.second; - SearchQueryParser.Trending parser = new SearchQueryParser.Trending(query); + SearchQueryParser.Marketplace parser = new SearchQueryParser.Marketplace(query); + + // TODO: parser.vendors on server + if (!parser.vendors.isEmpty()) { + for (IdeaPluginDescriptor descriptor : getPluginRepositories()) { + if (parser.vendors.contains(StringUtil.trim(descriptor.getVendor()))) { + result.descriptors.add(descriptor); + } + } + result.sortByName(); + return; + } if (!parser.repositories.isEmpty()) { for (String repository : parser.repositories) { @@ -467,6 +651,21 @@ public class PluginManagerConfigurableNewLayout result.descriptors.addAll(0, builtinList); } + + if (!result.descriptors.isEmpty()) { + String title = "Sort By"; + + for (AnAction action : myMarketplaceSortByGroup.getChildren(null)) { + MarketplaceSortByAction sortByAction = (MarketplaceSortByAction)action; + sortByAction.setState(parser); + if (sortByAction.myState) { + title = "Sort By: " + sortByAction.myOption.name(); + } + } + + myMarketplaceSortByAction.setText(title); + result.addRightAction(myMarketplaceSortByAction); + } } catch (IOException e) { PluginManagerMain.LOG.info(e); @@ -483,7 +682,25 @@ public class PluginManagerConfigurableNewLayout } private void createInstalledTab() { + myInstalledSearchGroup = new DefaultActionGroup(); + + for (InstalledSearchOption option : InstalledSearchOption.values()) { + myInstalledSearchGroup.add(new InstalledSearchOptionAction(option)); + } + myInstalledTab = new PluginsTab() { + @Override + protected void createSearchTextField() { + super.createSearchTextField(); + + JBTextField textField = mySearchTextField.getTextEditor(); + textField.putClientProperty("search.extension", ExtendableTextComponent.Extension + .create(AllIcons.Actions.More, AllIcons.Actions.More, "Search Options", // TODO: icon + () -> showRightBottomPopup(textField, "Show", myInstalledSearchGroup))); + textField.putClientProperty("JTextField.variant", null); + textField.putClientProperty("JTextField.variant", "search"); + } + @NotNull @Override protected PluginDetailsPageComponent createDetailsPanel(@NotNull LinkListener searchListener) { @@ -595,6 +812,16 @@ public class PluginManagerConfigurableNewLayout selectionListener.accept(myInstalledPanel); } + @Override + public void hideSearchPanel() { + super.hideSearchPanel(); + if (myInstalledSearchSetState) { + for (AnAction action : myInstalledSearchGroup.getChildren(null)) { + ((InstalledSearchOptionAction)action).setState(null); + } + } + } + @NotNull @Override protected SearchResultPanel createSearchPanel(@NotNull Consumer selectionListener, @@ -603,13 +830,14 @@ public class PluginManagerConfigurableNewLayout @NotNull @Override protected List getAttributes() { - return ContainerUtil.list("#disabled", "#enabled", "#bundled", "#custom", "#inactive", "#invalid", "#outdated", "#uninstalled"); + return ContainerUtil + .list("/disabled", "/enabled", "/bundled", "/custom", "/inactive", "/invalid", "/outdated", "/uninstalled", "/vendor:"); } @Nullable @Override protected List getValues(@NotNull String attribute) { - return null; + return "/vendor:".equals(attribute) ? myPluginModel.getVendors() : null; } @Override @@ -636,48 +864,122 @@ public class PluginManagerConfigurableNewLayout panel.setSelectionListener(selectionListener); PluginManagerConfigurableNew.registerCopyProvider(panel); + myInstalledSearchCallback = () -> { + StringBuilder queryBuilder = new StringBuilder(); + + for (AnAction action : myInstalledSearchGroup.getChildren(null)) { + InstalledSearchOptionAction optionAction = (InstalledSearchOptionAction)action; + + if (optionAction.myState) { + if (queryBuilder.length() > 0) { + queryBuilder.append(" "); + } + + switch (optionAction.myOption) { + case Enabled: + queryBuilder.append("/enabled"); + break; + case Disabled: + queryBuilder.append("/disabled"); + break; + case Custom: + queryBuilder.append("/custom"); + break; + case Bundled: + queryBuilder.append("/bundled"); + break; + case Invalid: + queryBuilder.append("/invalid"); + break; + case NeedUpdate: + queryBuilder.append("/outdated"); + break; + case Deleted: + queryBuilder.append("/uninstalled"); + break; + case NeedRestart: + queryBuilder.append("/inactive"); + break; + } + } + } + + try { + myInstalledSearchSetState = false; + + String query = queryBuilder.toString(); + searchTextField.setTextIgnoreEvents(query); + if (query.isEmpty()) { + myInstalledTab.hideSearchPanel(); + } + else { + myInstalledTab.showSearchPanel(query); + } + } + finally { + myInstalledSearchSetState = true; + } + }; + myInstalledSearchPanel = new SearchResultPanel(installedController, panel, 0, 0) { @Override protected void handleQuery(@NotNull String query, @NotNull PluginsGroup result) { InstalledPluginsState state = InstalledPluginsState.getInstance(); - SearchQueryParser.Installed parser = new SearchQueryParser.Installed(query); + SearchQueryParser.InstalledWithVendor parser = new SearchQueryParser.InstalledWithVendor(query); - for (UIPluginGroup uiGroup : myInstalledPanel.getGroups()) { - for (CellPluginComponent plugin : uiGroup.plugins) { - if (parser.attributes) { - if (parser.enabled != null && parser.enabled != myPluginModel.isEnabled(plugin.myPlugin)) { - continue; - } - if (parser.bundled != null && parser.bundled != plugin.myPlugin.isBundled()) { - continue; - } - if (parser.invalid != null && parser.invalid != myPluginModel.hasErrors(plugin.myPlugin)) { - continue; - } - if (parser.deleted != null) { - if (plugin.myPlugin instanceof IdeaPluginDescriptorImpl) { - if (parser.deleted != ((IdeaPluginDescriptorImpl)plugin.myPlugin).isDeleted()) { + if (myInstalledSearchSetState) { + for (AnAction action : myInstalledSearchGroup.getChildren(null)) { + ((InstalledSearchOptionAction)action).setState(parser); + } + } + + if (parser.vendors.isEmpty()) { + for (UIPluginGroup uiGroup : myInstalledPanel.getGroups()) { + for (CellPluginComponent plugin : uiGroup.plugins) { + if (parser.attributes) { + if (parser.enabled != null && parser.enabled != myPluginModel.isEnabled(plugin.myPlugin)) { + continue; + } + if (parser.bundled != null && parser.bundled != plugin.myPlugin.isBundled()) { + continue; + } + if (parser.invalid != null && parser.invalid != myPluginModel.hasErrors(plugin.myPlugin)) { + continue; + } + if (parser.deleted != null) { + if (plugin.myPlugin instanceof IdeaPluginDescriptorImpl) { + if (parser.deleted != ((IdeaPluginDescriptorImpl)plugin.myPlugin).isDeleted()) { + continue; + } + } + else if (parser.deleted) { continue; } } - else if (parser.deleted) { + PluginId pluginId = plugin.myPlugin.getPluginId(); + if (parser.needUpdate != null && parser.needUpdate != state.hasNewerVersion(pluginId)) { continue; } + if (parser.needRestart != null) { + if (parser.needRestart != (state.wasInstalled(pluginId) || state.wasUpdated(pluginId))) { + continue; + } + } } - PluginId pluginId = plugin.myPlugin.getPluginId(); - if (parser.needUpdate != null && parser.needUpdate != state.hasNewerVersion(pluginId)) { + if (parser.searchQuery != null && !StringUtil.containsIgnoreCase(plugin.myPlugin.getName(), parser.searchQuery)) { continue; } - if (parser.needRestart != null) { - if (parser.needRestart != (state.wasInstalled(pluginId) || state.wasUpdated(pluginId))) { - continue; - } + result.descriptors.add(plugin.myPlugin); + } + } + } + else { + for (UIPluginGroup uiGroup : myInstalledPanel.getGroups()) { + for (CellPluginComponent plugin : uiGroup.plugins) { + if (parser.vendors.contains(StringUtil.trim(plugin.myPlugin.getVendor()))) { + result.descriptors.add(plugin.myPlugin); } } - if (parser.searchQuery != null && !StringUtil.containsIgnoreCase(plugin.myPlugin.getName(), parser.searchQuery)) { - continue; - } - result.descriptors.add(plugin.myPlugin); } } } @@ -688,6 +990,136 @@ public class PluginManagerConfigurableNewLayout }; } + private enum SortBySearchOption { + Downloads, Name, Rating, Relevance, Updated + } + + private class MarketplaceSortByAction extends ToggleAction { + private final SortBySearchOption myOption; + private boolean myState; + + private MarketplaceSortByAction(@NotNull SortBySearchOption option) { + super(option.name()); + myOption = option; + } + + @Override + public boolean isSelected(@NotNull AnActionEvent e) { + return myState; + } + + @Override + public void setSelected(@NotNull AnActionEvent e, boolean state) { + myState = state; + myMarketplaceSortByCallback.accept(this); + } + + public void setState(@NotNull SearchQueryParser.Marketplace parser) { + if (parser.sortBy == null) { + myState = myOption == SortBySearchOption.Relevance; + } + else if (myOption == SortBySearchOption.Relevance) { + myState = "featured".equals(parser.sortBy); + } + else { + myState = myOption.name().equalsIgnoreCase(parser.sortBy); + } + } + + @NotNull + public String getQuery() { + switch (myOption) { + case Downloads: + return "/sortBy:downloads"; + case Name: + return "/sortBy:name"; + case Rating: + return "/sortBy:rating"; + case Updated: + return "/sortBy:updated"; + case Relevance: + default: + return "/sortBy:featured"; + } + } + } + + private enum InstalledSearchOption { + Enabled, Disabled, Custom, Bundled, Invalid, NeedUpdate, Deleted, NeedRestart + } + + private class InstalledSearchOptionAction extends ToggleAction { + private final InstalledSearchOption myOption; + private boolean myState; + + private InstalledSearchOptionAction(@NotNull InstalledSearchOption option) { + myOption = option; + + String text; + if (option == InstalledSearchOption.Custom) { + text = "Downloaded"; + } + else if (option == InstalledSearchOption.NeedUpdate) { + text = "Update available"; + } + else if (option == InstalledSearchOption.NeedRestart) { + text = "Installed or Updated"; + } + else { + text = option.name(); + } + getTemplatePresentation().setText(text); + } + + @Override + public boolean isSelected(@NotNull AnActionEvent e) { + return myState; + } + + @Override + public void setSelected(@NotNull AnActionEvent e, boolean state) { + myState = state; + myInstalledSearchCallback.run(); + } + + public void setState(@Nullable SearchQueryParser.Installed parser) { + if (parser == null) { + myState = false; + return; + } + + switch (myOption) { + case Enabled: + myState = parser.enabled != null && parser.enabled; + break; + case Disabled: + myState = parser.enabled != null && !parser.enabled; + break; + case Custom: + myState = parser.bundled != null && !parser.bundled; + break; + case Bundled: + myState = parser.bundled != null && parser.bundled; + break; + case Invalid: + myState = parser.invalid != null && parser.invalid; + break; + case NeedUpdate: + myState = parser.needUpdate != null && parser.needUpdate; + break; + case Deleted: + myState = parser.deleted != null && parser.deleted; + break; + case NeedRestart: + myState = parser.needRestart != null && parser.needRestart; + break; + } + } + } + + private static class GroupByActionGroup extends DefaultActionGroup implements CheckedActionGroup { + } + private class ChangePluginStateAction extends AnAction { private final boolean myEnable; diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/ErrorComponent.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/ErrorComponent.java index 1169b0255612..ac341886370b 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/ErrorComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/ErrorComponent.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 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.plugins.newui; +import com.intellij.ide.plugins.PluginManagerConfigurableNew; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.ui.DialogWrapper; @@ -23,9 +24,9 @@ public class ErrorComponent { private static final String KEY = "EnableCallback"; @NotNull - public static JComponent create(@NotNull JPanel panel, @Nullable Object constraints) { + public static JComponent create(@NotNull JPanel panel, boolean tinyFont, @Nullable Object constraints) { JEditorPane editorPane = new JEditorPane(); - panel.add(editorPane, constraints); + panel.add(tinyFont ? PluginManagerConfigurableNew.installTiny(editorPane) : editorPane, constraints); editorPane.setEditable(false); editorPane.setFocusable(false); @@ -67,12 +68,13 @@ public class ErrorComponent { @NotNull public static JComponent show(@NotNull JPanel panel, + boolean tinyFont, @Nullable Object constraints, @Nullable JComponent errorComponent, @NotNull String message, @Nullable String action, @Nullable Runnable enableCallback) { - JComponent component = errorComponent == null ? create(panel, constraints) : errorComponent; + JComponent component = errorComponent == null ? create(panel, tinyFont, constraints) : errorComponent; show(component, message, action, enableCallback); return component; } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java index 4456b2e39f27..530499acbf9a 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java @@ -10,6 +10,7 @@ import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.ex.ProgressIndicatorEx; import com.intellij.openapi.wm.ex.StatusBarEx; @@ -37,6 +38,7 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM private PluginsGroup myInstalling; private PluginsGroup myUpdates; private Configurable.TopComponentController myTopController; + private List myVendorsSorted; private static final Set myInstallingPlugins = new HashSet<>(); private static final Set myInstallingWithUpdatesPlugins = new HashSet<>(); @@ -387,6 +389,8 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM return; } + myVendorsSorted = null; + if (myDownloaded.ui == null) { myDownloaded.descriptors.add(descriptor); myDownloaded.titleWithEnabled(this); @@ -412,6 +416,23 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM } } + @NotNull + public List getVendors() { + if (ContainerUtil.isEmpty(myVendorsSorted)) { + assert myDownloaded != null; + + Set vendors = new HashSet<>(); + for (IdeaPluginDescriptor descriptor : myDownloaded.descriptors) { + String vendor = StringUtil.trim(descriptor.getVendor()); + if (!StringUtil.isEmptyOrSpaces(vendor)) { + vendors.add(vendor); + } + } + myVendorsSorted = ContainerUtil.sorted(vendors, String::compareToIgnoreCase); + } + return myVendorsSorted; + } + public boolean isEnabled(@NotNull IdeaPluginDescriptor plugin) { Boolean enabled = getEnabledMap().get(plugin.getPluginId()); return enabled == null || enabled; diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/NewListPluginComponent.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/NewListPluginComponent.java index 440c709d0848..0cffab0cec0e 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/NewListPluginComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/NewListPluginComponent.java @@ -254,8 +254,9 @@ public class NewListPluginComponent extends CellPluginComponent { Ref enableAction = new Ref<>(); String message = PluginManagerConfigurableNew.getErrorMessage(myPluginModel, myPlugin, enableAction); - myErrorComponent = ErrorComponent.show(myCenterPanel, VerticalLayout.FILL_HORIZONTAL, myErrorComponent, message, enableAction.get(), - enableAction.isNull() ? null : () -> myPluginModel.enableRequiredPlugins(myPlugin)); + myErrorComponent = ErrorComponent + .show(myCenterPanel, true, VerticalLayout.FILL_HORIZONTAL, myErrorComponent, message, enableAction.get(), + enableAction.isNull() ? null : () -> myPluginModel.enableRequiredPlugins(myPlugin)); if (addListeners) { myEventHandler.add(myErrorComponent); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginDetailsPageComponent.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginDetailsPageComponent.java index 60fca58cb9d9..b76434f7e6cc 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginDetailsPageComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginDetailsPageComponent.java @@ -132,7 +132,7 @@ public class PluginDetailsPageComponent extends MultiPanel { createButtons(); centerPanel.add(myNameAndButtons, VerticalLayout.FILL_HORIZONTAL); if (!myMarketplace) { - myErrorComponent = ErrorComponent.create(centerPanel, VerticalLayout.FILL_HORIZONTAL); + myErrorComponent = ErrorComponent.create(centerPanel, false, VerticalLayout.FILL_HORIZONTAL); } createMetricsPanel(centerPanel); @@ -208,9 +208,9 @@ public class PluginDetailsPageComponent extends MultiPanel { myDownloads = GridCellPluginComponent.createRatingLabel(panel1, null, "", AllIcons.Plugins.Downloads, CellPluginComponent.GRAY_COLOR, false); } - myVendor = new LinkPanel(panel1); + myVendor = new LinkPanel(panel1, false, null, TextHorizontalLayout.FIX_LABEL); - JPanel panel2 = new NonOpaquePanel(new TextHorizontalLayout(myMarketplace ? offset : JBUI.scale(7))); + JPanel panel2 = new NonOpaquePanel(new HorizontalLayout(myMarketplace ? offset : JBUI.scale(7))); if (myMarketplace) { panel2.add(myTagPanel = new TagPanel(mySearchListener)); } @@ -251,7 +251,7 @@ public class PluginDetailsPageComponent extends MultiPanel { bottomPanel.add(mySize = new JLabel()); } - myHomePage = new LinkPanel(bottomPanel); + myHomePage = new LinkPanel(bottomPanel, true, null, null); } public void showPlugin(@Nullable CellPluginComponent component) { @@ -354,7 +354,8 @@ public class PluginDetailsPageComponent extends MultiPanel { myVendor.hide(); } else { - myVendor.show(vendor, null); // TODO: link for search + myVendor.show(vendor, () -> mySearchListener + .linkSelected(null, "/vendor:" + (vendor.indexOf(' ') == -1 ? vendor : StringUtil.wrapWithDoubleQuote(vendor)))); } if (bundled) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginLogo.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginLogo.java index dcc9d0eb2e13..ba44bab91f2c 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginLogo.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginLogo.java @@ -51,6 +51,11 @@ public class PluginLogo { }); } + @NotNull + public static Icon getIcon(@NotNull IdeaPluginDescriptor descriptor, int width, int height) { + return null; + } + @NotNull public static Icon getIcon(@NotNull IdeaPluginDescriptor descriptor, boolean big, boolean jb, boolean error, boolean disabled) { return getIcon(descriptor).getIcon(big, jb, error, disabled); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsGroupComponent.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsGroupComponent.java index 4ba3932e123e..4bb56f809e54 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsGroupComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsGroupComponent.java @@ -112,7 +112,7 @@ public class PluginsGroupComponent extends JBPanelWithEmptyText { } } - private static final Color SECTION_HEADER_FOREGROUND = + public static final Color SECTION_HEADER_FOREGROUND = JBColor.namedColor("Plugins.SectionHeader.foreground", new JBColor(0x787878, 0x999999)); private static final Color SECTION_HEADER_BACKGROUND = JBColor.namedColor("Plugins.SectionHeader.background", new JBColor(0xF7F7F7, 0x3C3F41)); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsTab.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsTab.java index f557ba48573e..4a77a63f9e8d 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsTab.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginsTab.java @@ -38,7 +38,7 @@ public abstract class PluginsTab { private PluginDetailsPageComponent myDetailsPage; private MultiPanel myCardPanel; - private PluginSearchTextField mySearchTextField; + protected PluginSearchTextField mySearchTextField; private SearchResultPanel mySearchPanel; public final LinkListener mySearchListener = (__, data) -> { @@ -47,7 +47,7 @@ public abstract class PluginsTab { query = (String)data; } else if (data instanceof TagComponent) { - query = SearchQueryParser.getTagQuery(((TagComponent)data).getText()); + query = "/" + SearchQueryParser.getTagQuery(((TagComponent)data).getText()); } else { return; @@ -73,13 +73,9 @@ public abstract class PluginsTab { @Override public void addNotify() { super.addNotify(); - EventHandler.addGlobalAction(mySearchTextField, new CustomShortcutSet(KeyStroke.getKeyStroke("meta alt F")), () -> { - IdeFocusManager.getGlobalInstance() - .doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(mySearchTextField, true)); - if (mySearchPanel.controller != null) { - showSearchPopup(); - } - }); + EventHandler.addGlobalAction(mySearchTextField, new CustomShortcutSet(KeyStroke.getKeyStroke("meta alt F")), + () -> IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown( + () -> IdeFocusManager.getGlobalInstance().requestFocus(mySearchTextField, true))); } @Override @@ -115,7 +111,7 @@ public abstract class PluginsTab { return splitter; } - private void createSearchTextField() { + protected void createSearchTextField() { mySearchTextField = new PluginSearchTextField() { @Override protected boolean preprocessEventForTextField(KeyEvent event) { @@ -239,7 +235,7 @@ public abstract class PluginsTab { mySearchPanel.setQuery(query); } - private void hideSearchPanel() { + public void hideSearchPanel() { if (!mySearchPanel.isEmpty()) { myCardPanel.select(0, true); mySearchPanel.setQuery(""); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/SearchQueryParser.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/SearchQueryParser.java index 667f7f39afd0..d9e9b453d2b7 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/SearchQueryParser.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/SearchQueryParser.java @@ -94,7 +94,7 @@ public abstract class SearchQueryParser { return; } if (size == 1) { - searchQuery = words.get(0); + setSearchQuery(words.get(0)); return; } @@ -113,15 +113,19 @@ public abstract class SearchQueryParser { } } else if (searchQuery == null) { - searchQuery = name; + setSearchQuery(name); } else { - searchQuery = query; + setSearchQuery(query); return; } } } + protected void setSearchQuery(@NotNull String query) { + searchQuery = query; + } + protected abstract void handleAttribute(@NotNull String name, @NotNull String value, boolean invert); @NotNull @@ -138,6 +142,9 @@ public abstract class SearchQueryParser { parse(query); } + protected Trending() { + } + @NotNull public String getUrlQuery() { StringBuilder url = new StringBuilder(); @@ -189,6 +196,30 @@ public abstract class SearchQueryParser { } } + public static class Marketplace extends Trending { + public final Set vendors = new HashSet<>(); + + public Marketplace(@NotNull String query) { + parse(query); + } + + @Override + protected void handleAttribute(@NotNull String name, @NotNull String value, boolean invert) { + if (name.equals("/vendor")) { + vendors.add(value); + } + else if (name.startsWith("/")) { + super.handleAttribute(name.substring(1), value, invert); + } + } + + @NotNull + @Override + public String getUrlQuery() { + return super.getUrlQuery(); // TODO: Auto-generated method stub + } + } + public static class Installed extends SearchQueryParser { public Boolean enabled; // False == disabled public Boolean bundled; // False == custom @@ -196,9 +227,16 @@ public abstract class SearchQueryParser { public Boolean needUpdate; public Boolean deleted; public Boolean needRestart; // inactive & after update - public final boolean attributes; + public boolean attributes; public Installed(@NotNull String query) { + localParse(query); + } + + protected Installed() { + } + + private void localParse(@NotNull String query) { for (String word : splitQuery(query)) { if (word.startsWith("#")) { handleAttribute(word.substring(1), "", false); @@ -214,7 +252,10 @@ public abstract class SearchQueryParser { break; } } + parseEnd(); + } + protected void parseEnd() { attributes = enabled != null || bundled != null || invalid != null || needUpdate != null || deleted != null || needRestart != null; } @@ -253,4 +294,49 @@ public abstract class SearchQueryParser { } } } + + public static class InstalledWithVendor extends Installed { + public final Set vendors = new HashSet<>(); + + public InstalledWithVendor(@NotNull String query) { + localParse(query); + } + + private void localParse(@NotNull String query) { + List words = splitQuery(query); + int size = words.size(); + + if (size == 0) { + return; + } + + int index = 0; + while (index < size) { + String name = words.get(index++); + if (name.startsWith("/")) { + if (name.equals("/vendor:")) { + if (index < size) { + vendors.add(words.get(index++)); + } + else { + searchQuery = query; + break; + } + } + else { + handleAttribute(name.substring(1), "", false); + } + } + else if (searchQuery == null) { + searchQuery = name; + } + else { + searchQuery = query; + break; + } + } + + parseEnd(); + } + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java index 79d14a8d9a60..b50d9cf5999a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java @@ -535,6 +535,10 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple } } else if ("search".equals(variant)) { + Object extension = getComponent().getClientProperty("search.extension"); + if (extension instanceof Extension) { + addExtension((Extension)extension); + } addExtension(new SearchExtension()); addExtension(new ClearExtension()); }