/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.ide.plugins; import com.intellij.icons.AllIcons; import com.intellij.ide.IdeBundle; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.JDOMExternalizableStringList; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FileStatus; import com.intellij.ui.BooleanTableCellEditor; import com.intellij.ui.BooleanTableCellRenderer; import com.intellij.ui.JBColor; import com.intellij.util.Function; import com.intellij.util.containers.hash.HashSet; import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import java.awt.*; import java.util.*; import java.util.List; /** * Created by IntelliJ IDEA. * User: stathik * Date: Dec 26, 2003 * Time: 3:51:58 PM * To change this template use Options | File Templates. */ public class InstalledPluginsTableModel extends PluginTableModel { public static Map<PluginId, Integer> NewVersions2Plugins = new HashMap<PluginId, Integer>(); public static Set<PluginId> updatedPlugins = new HashSet<PluginId>(); private final Map<PluginId, Boolean> myEnabled = new HashMap<PluginId, Boolean>(); private final Map<PluginId, Set<PluginId>> myDependentToRequiredListMap = new HashMap<PluginId, Set<PluginId>>(); private static final String ENABLED_DISABLED = "All plugins"; private static final String ENABLED = "Enabled plugins"; private static final String DISABLED = "Disabled plugins"; public static final String[] ENABLED_VALUES = new String[]{ENABLED_DISABLED, ENABLED, DISABLED}; private String myEnabledFilter = ENABLED_DISABLED; private static final Set<IdeaPluginDescriptor> myInstalled = new HashSet<IdeaPluginDescriptor>(); public InstalledPluginsTableModel() { super.columns = new ColumnInfo[]{new MyPluginManagerColumnInfo(), new EnabledPluginInfo()}; view = new ArrayList<IdeaPluginDescriptor>(Arrays.asList(PluginManager.getPlugins())); view.addAll(myInstalled); reset(view); for (Iterator<IdeaPluginDescriptor> iterator = view.iterator(); iterator.hasNext(); ) { @NonNls final String s = iterator.next().getPluginId().getIdString(); if (PluginManagerCore.CORE_PLUGIN_ID.equals(s)) iterator.remove(); } setSortKey(new RowSorter.SortKey(getNameColumn(), SortOrder.ASCENDING)); } public boolean appendOrUpdateDescriptor(IdeaPluginDescriptor descriptor) { final PluginId descrId = descriptor.getPluginId(); final IdeaPluginDescriptor existing = PluginManager.getPlugin(descrId); if (existing != null) { updateExistingPlugin(descriptor, existing); return true; } else if (!myInstalled.contains(descriptor)) { myInstalled.add(descriptor); view.add(descriptor); setEnabled(descriptor, true); fireTableDataChanged(); return true; } return false; } public static void updateExistingPlugin(IdeaPluginDescriptor descriptor, @Nullable IdeaPluginDescriptor existing) { if (existing != null) { updateExistingPluginInfo(descriptor, existing); updatedPlugins.add(existing.getPluginId()); } } public static int getCheckboxColumn() { return 1; } @Override public int getNameColumn() { return 0; } private void reset(final List<IdeaPluginDescriptor> list) { for (IdeaPluginDescriptor ideaPluginDescriptor : list) { setEnabled(ideaPluginDescriptor); } updatePluginDependencies(); } private void setEnabled(IdeaPluginDescriptor ideaPluginDescriptor) { setEnabled(ideaPluginDescriptor, ideaPluginDescriptor.isEnabled()); } private void setEnabled(IdeaPluginDescriptor ideaPluginDescriptor, final boolean enabled) { final Collection<String> disabledPlugins = PluginManager.getDisabledPlugins(); final PluginId pluginId = ideaPluginDescriptor.getPluginId(); if (!enabled && !disabledPlugins.contains(pluginId.toString())) { myEnabled.put(pluginId, null); } else { myEnabled.put(pluginId, enabled); } } public Map<PluginId, Set<PluginId>> getDependentToRequiredListMap() { return myDependentToRequiredListMap; } protected void updatePluginDependencies() { myDependentToRequiredListMap.clear(); final int rowCount = getRowCount(); for (int i = 0; i < rowCount; i++) { final IdeaPluginDescriptor descriptor = getObjectAt(i); final PluginId pluginId = descriptor.getPluginId(); myDependentToRequiredListMap.remove(pluginId); if (descriptor instanceof IdeaPluginDescriptorImpl && ((IdeaPluginDescriptorImpl)descriptor).isDeleted()) continue; final Boolean enabled = myEnabled.get(pluginId); if (enabled == null || enabled.booleanValue()) { PluginManager.checkDependants(descriptor, new Function<PluginId, IdeaPluginDescriptor>() { @Override @Nullable public IdeaPluginDescriptor fun(final PluginId pluginId) { return PluginManager.getPlugin(pluginId); } }, new Condition<PluginId>() { @Override public boolean value(final PluginId dependantPluginId) { final Boolean enabled = myEnabled.get(dependantPluginId); if ((enabled == null && !updatedPlugins.contains(dependantPluginId)) || (enabled != null && !enabled.booleanValue())) { Set<PluginId> required = myDependentToRequiredListMap.get(pluginId); if (required == null) { required = new HashSet<PluginId>(); myDependentToRequiredListMap.put(pluginId, required); } required.add(dependantPluginId); //return false; } return true; } } ); if (enabled == null && !myDependentToRequiredListMap.containsKey(pluginId) && !PluginManager.isIncompatible(descriptor)) { myEnabled.put(pluginId, true); } } } } @Override public void updatePluginsList(List<IdeaPluginDescriptor> list) { // For each downloadable plugin we need to know whether its counterpart // is already installed, and if yes compare the difference in versions: // availability of newer versions will be indicated separately. for (IdeaPluginDescriptor descr : list) { PluginId descrId = descr.getPluginId(); IdeaPluginDescriptor existing = PluginManager.getPlugin(descrId); if (existing != null) { if (descr instanceof PluginNode) { updateExistingPluginInfo(descr, existing); } else { view.add(descr); setEnabled(descr); } } } for (IdeaPluginDescriptor descriptor : myInstalled) { if (!view.contains(descriptor)) { view.add(descriptor); } } fireTableDataChanged(); } @Override protected ArrayList<IdeaPluginDescriptor> toProcess() { ArrayList<IdeaPluginDescriptor> toProcess = super.toProcess(); for (IdeaPluginDescriptor descriptor : myInstalled) { if (!toProcess.contains(descriptor)) { toProcess.add(descriptor); } } return toProcess; } @Override public void filter(final List<IdeaPluginDescriptor> filtered) { view.clear(); for (IdeaPluginDescriptor descriptor : filtered) { view.add(descriptor); } super.filter(filtered); } private static void updateExistingPluginInfo(IdeaPluginDescriptor descr, IdeaPluginDescriptor existing) { int state = StringUtil.compareVersionNumbers(descr.getVersion(), existing.getVersion()); final PluginId pluginId = existing.getPluginId(); final String idString = pluginId.getIdString(); final JDOMExternalizableStringList installedPlugins = PluginManagerUISettings.getInstance().getInstalledPlugins(); if (!installedPlugins.contains(idString) && !((IdeaPluginDescriptorImpl)existing).isDeleted()) { installedPlugins.add(idString); } final PluginManagerUISettings updateSettings = PluginManagerUISettings.getInstance(); if (state > 0 && !PluginManager.isIncompatible(descr) && !updatedPlugins.contains(descr.getPluginId())) { NewVersions2Plugins.put(pluginId, 1); if (!updateSettings.myOutdatedPlugins.contains(idString)) { updateSettings.myOutdatedPlugins.add(idString); } final IdeaPluginDescriptorImpl plugin = (IdeaPluginDescriptorImpl)existing; plugin.setDownloadsCount(descr.getDownloads()); plugin.setVendor(descr.getVendor()); plugin.setVendorEmail(descr.getVendorEmail()); plugin.setVendorUrl(descr.getVendorUrl()); plugin.setUrl(descr.getUrl()); } else { updateSettings.myOutdatedPlugins.remove(idString); if (NewVersions2Plugins.remove(pluginId) != null) { updatedPlugins.add(pluginId); } } } public void enableRows(IdeaPluginDescriptor[] ideaPluginDescriptors, Boolean value) { for (IdeaPluginDescriptor ideaPluginDescriptor : ideaPluginDescriptors) { final PluginId currentPluginId = ideaPluginDescriptor.getPluginId(); final Boolean enabled = myEnabled.get(currentPluginId) == null ? Boolean.FALSE : value; myEnabled.put(currentPluginId, enabled); } updatePluginDependencies(); warnAboutMissedDependencies(value, ideaPluginDescriptors); hideNotApplicablePlugins(value, ideaPluginDescriptors); } private void hideNotApplicablePlugins(Boolean value, final IdeaPluginDescriptor... ideaPluginDescriptors) { if (!value && ENABLED.equals(myEnabledFilter) || (value && DISABLED.equals(myEnabledFilter))) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { for (IdeaPluginDescriptor ideaPluginDescriptor : ideaPluginDescriptors) { view.remove(ideaPluginDescriptor); filtered.add(ideaPluginDescriptor); } fireTableDataChanged(); } }); } } public static boolean hasNewerVersion(PluginId descr) { return !wasUpdated(descr) && (NewVersions2Plugins.containsKey(descr) || PluginManagerUISettings.getInstance().myOutdatedPlugins.contains(descr.getIdString())); } public static boolean wasUpdated(PluginId descr) { return updatedPlugins.contains(descr); } public boolean isEnabled(final PluginId pluginId) { final Boolean enabled = myEnabled.get(pluginId); return enabled != null && enabled.booleanValue(); } public boolean isDisabled(final PluginId pluginId) { final Boolean enabled = myEnabled.get(pluginId); return enabled != null && !enabled.booleanValue(); } public Map<PluginId, Boolean> getEnabledMap() { return myEnabled; } public String getEnabledFilter() { return myEnabledFilter; } public void setEnabledFilter(String enabledFilter, String filter) { myEnabledFilter = enabledFilter; filter(filter); } @Override public boolean isPluginDescriptorAccepted(IdeaPluginDescriptor descriptor) { if (!myEnabledFilter.equals(ENABLED_DISABLED)) { final boolean enabled = isEnabled(descriptor.getPluginId()); if (enabled && myEnabledFilter.equals(DISABLED)) return false; if (!enabled && myEnabledFilter.equals(ENABLED)) return false; } return true; } private class EnabledPluginInfo extends ColumnInfo<IdeaPluginDescriptor, Boolean> { public EnabledPluginInfo() { super(/*IdeBundle.message("plugin.manager.enable.column.title")*/""); } @Override public Boolean valueOf(IdeaPluginDescriptor ideaPluginDescriptor) { return myEnabled.get(ideaPluginDescriptor.getPluginId()); } @Override public boolean isCellEditable(final IdeaPluginDescriptor ideaPluginDescriptor) { return true; } @Override public Class getColumnClass() { return Boolean.class; } @Override public TableCellEditor getEditor(final IdeaPluginDescriptor o) { return new BooleanTableCellEditor(); } @Override public TableCellRenderer getRenderer(final IdeaPluginDescriptor ideaPluginDescriptor) { return new BooleanTableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return super.getTableCellRendererComponent(table, value == null ? Boolean.TRUE : value, isSelected, hasFocus, row, column); } }; } @Override public void setValue(final IdeaPluginDescriptor ideaPluginDescriptor, Boolean value) { final PluginId currentPluginId = ideaPluginDescriptor.getPluginId(); final Boolean enabled = myEnabled.get(currentPluginId) == null ? Boolean.FALSE : value; myEnabled.put(currentPluginId, enabled); updatePluginDependencies(); warnAboutMissedDependencies(enabled, ideaPluginDescriptor); hideNotApplicablePlugins(value, ideaPluginDescriptor); } @Override public Comparator<IdeaPluginDescriptor> getComparator() { return new Comparator<IdeaPluginDescriptor>() { @Override public int compare(final IdeaPluginDescriptor o1, final IdeaPluginDescriptor o2) { final Boolean enabled1 = myEnabled.get(o1.getPluginId()); final Boolean enabled2 = myEnabled.get(o2.getPluginId()); if (enabled1 != null && enabled1.booleanValue()) { if (enabled2 != null && enabled2.booleanValue()) { return 0; } return 1; } else { if (enabled2 == null || !enabled2.booleanValue()) { return 0; } return -1; } } }; } @Override public int getWidth(JTable table) { return new JCheckBox().getPreferredSize().width; } } private void warnAboutMissedDependencies(final Boolean newVal, final IdeaPluginDescriptor... ideaPluginDescriptors) { final Set<PluginId> deps = new HashSet<PluginId>(); final List<IdeaPluginDescriptor> descriptorsToCheckDependencies = new ArrayList<IdeaPluginDescriptor>(); if (newVal) { Collections.addAll(descriptorsToCheckDependencies, ideaPluginDescriptors); } else { descriptorsToCheckDependencies.addAll(getAllPlugins()); descriptorsToCheckDependencies.removeAll(Arrays.asList(ideaPluginDescriptors)); for (Iterator<IdeaPluginDescriptor> iterator = descriptorsToCheckDependencies.iterator(); iterator.hasNext(); ) { IdeaPluginDescriptor descriptor = iterator.next(); final Boolean enabled = myEnabled.get(descriptor.getPluginId()); if (enabled == null || !enabled.booleanValue()) { iterator.remove(); } } } for (final IdeaPluginDescriptor ideaPluginDescriptor : descriptorsToCheckDependencies) { PluginManager.checkDependants(ideaPluginDescriptor, new Function<PluginId, IdeaPluginDescriptor>() { @Override @Nullable public IdeaPluginDescriptor fun(final PluginId pluginId) { return PluginManager.getPlugin(pluginId); } }, new Condition<PluginId>() { @Override public boolean value(final PluginId pluginId) { Boolean enabled = myEnabled.get(pluginId); if (enabled == null) { return false; } if (newVal && !enabled.booleanValue()) { deps.add(pluginId); } if (!newVal) { if (ideaPluginDescriptor instanceof IdeaPluginDescriptorImpl && ((IdeaPluginDescriptorImpl)ideaPluginDescriptor).isDeleted()) { return true; } final PluginId pluginDescriptorId = ideaPluginDescriptor.getPluginId(); for (IdeaPluginDescriptor descriptor : ideaPluginDescriptors) { if (pluginId.equals(descriptor.getPluginId())) { deps.add(pluginDescriptorId); break; } } } return true; } } ); } if (!deps.isEmpty()) { final String listOfSelectedPlugins = StringUtil.join(ideaPluginDescriptors, new Function<IdeaPluginDescriptor, String>() { @Override public String fun(IdeaPluginDescriptor pluginDescriptor) { return pluginDescriptor.getName(); } }, ", "); final Set<IdeaPluginDescriptor> pluginDependencies = new HashSet<IdeaPluginDescriptor>(); final String listOfDependencies = StringUtil.join(deps, new Function<PluginId, String>() { @Override public String fun(final PluginId pluginId) { final IdeaPluginDescriptor pluginDescriptor = PluginManager.getPlugin(pluginId); assert pluginDescriptor != null; pluginDependencies.add(pluginDescriptor); return pluginDescriptor.getName(); } }, "<br>"); final String message = !newVal ? "<html>The following plugins <br>" + listOfDependencies + "<br>are enabled and depend" + (deps.size() == 1 ? "s" : "") + " on selected plugins. " + "<br>Would you like to disable them too?</html>" : "<html>The following plugins on which " + listOfSelectedPlugins + " depend" + (ideaPluginDescriptors.length == 1 ? "s" : "") + " are disabled:<br>" + listOfDependencies + "<br>Would you like to enable them?</html>"; if (Messages.showOkCancelDialog(message, newVal ? "Enable Dependant Plugins" : "Disable Plugins with Dependency on this", Messages.getQuestionIcon()) == Messages.OK) { for (PluginId pluginId : deps) { myEnabled.put(pluginId, newVal); } updatePluginDependencies(); hideNotApplicablePlugins(newVal, pluginDependencies.toArray(new IdeaPluginDescriptor[pluginDependencies.size()])); } } } private class InstalledPluginsTableRenderer extends DefaultTableCellRenderer { private JLabel myNameLabel = new JLabel(); private JLabel myBundledLabel = new JLabel(); private JPanel myPanel = new JPanel(new BorderLayout()); private final IdeaPluginDescriptor myPluginDescriptor; public InstalledPluginsTableRenderer(IdeaPluginDescriptor pluginDescriptor) { myPluginDescriptor = pluginDescriptor; myNameLabel.setFont(PluginManagerColumnInfo.getNameFont()); myBundledLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); myPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 1)); myNameLabel.setOpaque(true); myPanel.add(myNameLabel, BorderLayout.WEST); myPanel.add(myBundledLabel, BorderLayout.EAST); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final Component orig = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (myPluginDescriptor != null) { myNameLabel.setText(myPluginDescriptor.getName()); final PluginId pluginId = myPluginDescriptor.getPluginId(); final String idString = pluginId.getIdString(); if (myPluginDescriptor.isBundled()) { myBundledLabel.setText("Bundled"); } else { if (PluginManagerUISettings.getInstance().getInstalledPlugins().contains(idString)) { myBundledLabel.setText("From repository"); } else { myBundledLabel.setText("Custom"); } } if (myPluginDescriptor instanceof IdeaPluginDescriptorImpl && ((IdeaPluginDescriptorImpl)myPluginDescriptor).isDeleted()) { myNameLabel.setIcon(AllIcons.Actions.Clean); } else if (hasNewerVersion(pluginId)) { myNameLabel.setIcon(AllIcons.Nodes.Pluginobsolete); myPanel.setToolTipText("Newer version of the plugin is available"); } else { myNameLabel.setIcon(AllIcons.Nodes.Plugin); } final Color fg = orig.getForeground(); final Color bg = orig.getBackground(); final Color grayedFg = isSelected ? fg : Color.GRAY; myPanel.setBackground(bg); myNameLabel.setBackground(bg); myBundledLabel.setBackground(bg); myNameLabel.setForeground(fg); final boolean wasUpdated = wasUpdated(pluginId); if (wasUpdated || PluginManager.getPlugin(pluginId) == null) { if (!isSelected) { myNameLabel.setForeground(FileStatus.COLOR_ADDED); } if (wasUpdated) { myPanel.setToolTipText("Plugin was updated to the newest version. Changes will be available after restart"); } else { myPanel.setToolTipText("Plugin will be activated after restart."); } } myBundledLabel.setForeground(grayedFg); final Set<PluginId> required = myDependentToRequiredListMap.get(pluginId); if (required != null && required.size() > 0) { myNameLabel.setForeground(JBColor.RED); final StringBuilder s = new StringBuilder(); if (myEnabled.get(pluginId) == null) { s.append("Plugin was not loaded.\n"); } if (required.contains(PluginId.getId("com.intellij.modules.ultimate"))) { s.append("The plugin requires IntelliJ IDEA Ultimate"); } else { s.append("Required plugin").append(required.size() == 1 ? " \"" : "s \""); s.append(StringUtil.join(required, new Function<PluginId, String>() { @Override public String fun(final PluginId id) { final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id); return plugin == null ? id.getIdString() : plugin.getName(); } }, ",")); s.append(required.size() == 1 ? "\" is not enabled." : "\" are not enabled."); } myPanel.setToolTipText(s.toString()); } if (PluginManager.isIncompatible(myPluginDescriptor)) { myPanel.setToolTipText(IdeBundle.message("plugin.manager.incompatible.tooltip.warning", ApplicationNamesInfo.getInstance().getFullProductName())); myNameLabel.setForeground(JBColor.RED); } } return myPanel; } } private class MyPluginManagerColumnInfo extends PluginManagerColumnInfo { public MyPluginManagerColumnInfo() { super(PluginManagerColumnInfo.COLUMN_NAME, InstalledPluginsTableModel.this); } @Override public TableCellRenderer getRenderer(final IdeaPluginDescriptor pluginDescriptor) { return new PluginsTableRenderer(pluginDescriptor, false); } @Override protected boolean isSortByName() { return true; } @Override public Comparator<IdeaPluginDescriptor> getComparator() { final Comparator<IdeaPluginDescriptor> comparator = super.getColumnComparator(); return new Comparator<IdeaPluginDescriptor>() { @Override public int compare(IdeaPluginDescriptor o1, IdeaPluginDescriptor o2) { if (isSortByStatus()) { final boolean incompatible1 = PluginManager.isIncompatible(o1); final boolean incompatible2 = PluginManager.isIncompatible(o2); if (incompatible1) { if (incompatible2) return comparator.compare(o1, o2); return -1; } if (incompatible2) return 1; final boolean hasNewerVersion1 = hasNewerVersion(o1.getPluginId()); final boolean hasNewerVersion2 = hasNewerVersion(o2.getPluginId()); if (hasNewerVersion1) { if (hasNewerVersion2) return comparator.compare(o1, o2); return -1; } if (hasNewerVersion2) return 1; final boolean wasUpdated1 = wasUpdated(o1.getPluginId()); final boolean wasUpdated2 = wasUpdated(o2.getPluginId()); if (wasUpdated1) { if (wasUpdated2) return comparator.compare(o1, o2); return -1; } if (wasUpdated2) return 1; if (o1 instanceof PluginNode) { if (o2 instanceof PluginNode) return comparator.compare(o1, o2); return -1; } if (o2 instanceof PluginNode) return 1; final boolean deleted1 = o1 instanceof IdeaPluginDescriptorImpl && ((IdeaPluginDescriptorImpl)o1).isDeleted(); final boolean deleted2 = o2 instanceof IdeaPluginDescriptorImpl && ((IdeaPluginDescriptorImpl)o2).isDeleted(); if (deleted1) { if (deleted2) return comparator.compare(o1, o2); return -1; } if (deleted2) return 1; final boolean enabled1 = isEnabled(o1.getPluginId()); final boolean enabled2 = isEnabled(o2.getPluginId()); if (enabled1 && !enabled2) return -1; if (enabled2 && !enabled1) return 1; } return comparator.compare(o1, o2); } }; } @Override public int getWidth(JTable table) { return super.getWidth(table); } } }