From f1df9e843231c0885b78e7a7c8c6e349dec3def8 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Fri, 30 Aug 2013 19:14:20 +0200 Subject: [PATCH] packaging UI moved to lang-impl --- .../webcore/packaging/InstalledPackage.java | 51 ++ .../packaging/InstalledPackagesPanel.java | 477 +++++++++++++++ .../packaging/ManagePackagesDialog.form | 181 ++++++ .../packaging/ManagePackagesDialog.java | 563 ++++++++++++++++++ .../webcore/packaging/ManageRepoDialog.form | 11 + .../webcore/packaging/ManageRepoDialog.java | 116 ++++ .../packaging/PackageManagementService.java | 139 +++++ .../packaging/PackageManagementServiceEx.java | 15 + .../packaging/PackageVersionComparator.java | 83 +++ .../packaging/PackagesNotificationPanel.java | 128 ++++ .../webcore/packaging/RepoPackage.java | 44 ++ 11 files changed, 1808 insertions(+) create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackage.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.form create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.form create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementServiceEx.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/PackageVersionComparator.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java create mode 100644 platform/lang-impl/src/com/intellij/webcore/packaging/RepoPackage.java diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackage.java b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackage.java new file mode 100644 index 000000000000..0fe693f22e9f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackage.java @@ -0,0 +1,51 @@ +package com.intellij.webcore.packaging; + +import org.jetbrains.annotations.Nullable; + +/** + * @author yole + */ +public class InstalledPackage { + private final String myName; + private final String myVersion; + + public InstalledPackage(String name, String version) { + myName = name; + myVersion = version; + } + + public String getName() { + return myName; + } + + public String getVersion() { + return myVersion; + } + + @Nullable + public String getTooltipText() { + return null; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InstalledPackage aPackage = (InstalledPackage)o; + if (myName != null ? !myName.equals(aPackage.myName) : aPackage.myName != null) return false; + if (myVersion != null ? !myVersion.equals(aPackage.myVersion) : aPackage.myVersion != null) return false; + return true; + } + + @Override + public int hashCode() { + int result = myName != null ? myName.hashCode() : 0; + result = 31 * result + (myVersion != null ? myVersion.hashCode() : 0); + return result; + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java new file mode 100644 index 000000000000..bfa99455c1a0 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/InstalledPackagesPanel.java @@ -0,0 +1,477 @@ +package com.intellij.webcore.packaging; + +import com.google.common.collect.Lists; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.Messages; +import com.intellij.ui.DoubleClickListener; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.table.JBTable; +import com.intellij.util.CatchingConsumer; +import com.intellij.util.Consumer; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public class InstalledPackagesPanel extends JPanel { + protected final JButton myInstallButton; + private final JButton myUninstallButton; + private final JButton myUpgradeButton; + + protected final JBTable myPackagesTable; + private DefaultTableModel myPackagesTableModel; + protected PackageManagementService myPackageManagementService; + protected final Project myProject; + protected final PackagesNotificationPanel myNotificationArea; + protected final List> myPathChangedListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + private final Set myCurrentlyInstalling = ContainerUtil.newHashSet(); + private final Set myWaitingToUpgrade = ContainerUtil.newHashSet(); + + public InstalledPackagesPanel(Project project, PackagesNotificationPanel area) { + super(new GridBagLayout()); + myProject = project; + myNotificationArea = area; + myInstallButton = new JButton("Install"); + myUninstallButton = new JButton("Uninstall"); + myUpgradeButton = new JButton("Upgrade"); + myInstallButton.setMnemonic('I'); + myUninstallButton.setMnemonic('U'); + myUpgradeButton.setMnemonic('p'); + + myInstallButton.setEnabled(false); + myUninstallButton.setEnabled(false); + myUpgradeButton.setEnabled(false); + + myPackagesTableModel = new DefaultTableModel(new String[]{"Package", "Version", "Latest"}, 0) { + @Override + public boolean isCellEditable(int i, int i1) { + return false; + } + }; + final TableCellRenderer tableCellRenderer = new MyTableCellRenderer(); + myPackagesTable = new JBTable(myPackagesTableModel) { + @Override + public TableCellRenderer getCellRenderer(int row, int column) { + return tableCellRenderer; + } + }; + myPackagesTable.getTableHeader().setReorderingAllowed(false); + + Insets anInsets = new Insets(2, 2, 2, 2); + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myPackagesTable, + ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setPreferredSize(new Dimension(500, 500)); + add(scrollPane, new GridBagConstraints(0, 0, 1, 3, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + anInsets, 0, 0)); + + addUninstallAction(); + addUpgradeAction(); + + add(myInstallButton, + new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + anInsets, 0, 0)); + add(myUninstallButton, + new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + anInsets, 0, 0)); + add(myUpgradeButton, + new GridBagConstraints(1, 2, 1, 1, 0.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, anInsets, 0, 0)); + + myPackagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent event) { + updateUninstallUpgrade(); + } + }); + + myInstallButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (myPackageManagementService != null) { + ManagePackagesDialog dialog = createManagePackagesDialog(); + dialog.show(); + } + } + }); + + new DoubleClickListener() { + @Override + protected boolean onDoubleClick(MouseEvent e) { + if (myPackageManagementService != null && myInstallButton.isEnabled()) { + ManagePackagesDialog dialog = createManagePackagesDialog(); + Point p = e.getPoint(); + int row = myPackagesTable.rowAtPoint(p); + int column = myPackagesTable.columnAtPoint(p); + if (row >= 0 && column >= 0) { + Object pkg = myPackagesTable.getValueAt(row, 0); + if (pkg instanceof InstalledPackage) { + dialog.selectPackage((InstalledPackage) pkg); + } + } + dialog.show(); + return true; + } + return false; + } + }.installOn(myPackagesTable); + } + + private ManagePackagesDialog createManagePackagesDialog() { + return new ManagePackagesDialog(myProject, + myPackageManagementService, + new PackageManagementService.Listener() { + @Override + public void operationStarted(String packageName) { + myPackagesTable.setPaintBusy(true); + } + + @Override + public void operationFinished(String packageName, + @Nullable String errorDescription) { + myNotificationArea.showResult(packageName, errorDescription); + myPackagesTable.clearSelection(); + doUpdatePackages(myPackageManagementService); + } + }); + } + + public void addPathChangedListener(Consumer consumer) { + myPathChangedListeners.add(consumer); + } + + private void addUpgradeAction() { + myUpgradeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final int[] rows = myPackagesTable.getSelectedRows(); + if (myPackageManagementService != null) { + final Set upgradedPackages = new HashSet(); + final Set packagesShouldBePostponed = getPackagesToPostpone(); + for (int row : rows) { + final Object packageObj = myPackagesTableModel.getValueAt(row, 0); + if (packageObj instanceof InstalledPackage) { + InstalledPackage pkg = (InstalledPackage)packageObj; + final String packageName = pkg.getName(); + final String currentVersion = pkg.getVersion(); + final String availableVersion = (String)myPackagesTableModel.getValueAt(row, 2); + + if (packagesShouldBePostponed.contains(packageName)) { + myWaitingToUpgrade.add((InstalledPackage)packageObj); + } + else if (PackageVersionComparator.VERSION_COMPARATOR.compare(currentVersion, availableVersion) < 0) { + upgradePackage(pkg, availableVersion); + upgradedPackages.add(packageName); + } + } + } + + if (myCurrentlyInstalling.isEmpty() && upgradedPackages.isEmpty() && !myWaitingToUpgrade.isEmpty()) { + upgradePostponedPackages(); + } + } + } + }); + } + + private void upgradePostponedPackages() { + final Iterator iterator = myWaitingToUpgrade.iterator(); + final InstalledPackage toUpgrade = iterator.next(); + iterator.remove(); + upgradePackage(toUpgrade, toUpgrade.getVersion()); + } + + protected Set getPackagesToPostpone() { + return Collections.emptySet(); + } + + private void upgradePackage(@NotNull final InstalledPackage pkg, @Nullable final String toVersion) { + final PackageManagementService selPackageManagementService = myPackageManagementService; + myPackageManagementService.fetchPackageVersions(pkg.getName(), new CatchingConsumer, Exception>() { + @Override + public void consume(List releases) { + if (!releases.isEmpty() && + PackageVersionComparator.VERSION_COMPARATOR.compare(pkg.getVersion(), releases.get(0)) >= 0) { + return; + } + + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + final PackageManagementService.Listener listener = new PackageManagementService.Listener() { + @Override + public void operationStarted(String packageName) { + myPackagesTable.setPaintBusy(true); + myCurrentlyInstalling.add(packageName); + } + + @Override + public void operationFinished(String packageName, @Nullable String errorDescription) { + myPackagesTable.clearSelection(); + updatePackages(selPackageManagementService); + myPackagesTable.setPaintBusy(false); + myCurrentlyInstalling.remove(packageName); + if (errorDescription == null) { + myNotificationArea.showSuccess("Package " + packageName + " successfully upgraded"); + } + else { + myNotificationArea.showError("Upgrade packages failed. Details...", + "Upgrade Packages Failed", + "Upgrade packages failed.\n" + errorDescription); + } + + if (myCurrentlyInstalling.isEmpty() && !myWaitingToUpgrade.isEmpty()) { + upgradePostponedPackages(); + } + } + }; + PackageManagementServiceEx serviceEx = getServiceEx(); + if (serviceEx != null) { + serviceEx.updatePackage(pkg, toVersion, listener); + } + else { + myPackageManagementService.installPackage(new RepoPackage(pkg.getName(), null /* TODO? */), null, true, null, listener, false); + } + myUpgradeButton.setEnabled(false); + } + }, ModalityState.any()); + } + + @Override + public void consume(Exception e) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + Messages.showErrorDialog("Error occurred. Please, check your internet connection.", + "Upgrade Package Failed."); + } + }, ModalityState.any()); + } + }); + } + + @Nullable + private PackageManagementServiceEx getServiceEx() { + return ObjectUtils.tryCast(myPackageManagementService, PackageManagementServiceEx.class); + } + + private void updateUninstallUpgrade() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + final int[] selected = myPackagesTable.getSelectedRows(); + boolean upgradeAvailable = false; + boolean canUninstall = true; + boolean canUpgrade = true; + if (myPackageManagementService != null && selected.length != 0) { + for (int i = 0; i != selected.length; ++i) { + final int index = selected[i]; + if (index >= myPackagesTable.getRowCount()) continue; + final Object value = myPackagesTable.getValueAt(index, 0); + if (value instanceof InstalledPackage) { + final InstalledPackage pkg = (InstalledPackage)value; + if (!canUninstallPackage(pkg)) { + canUninstall = false; + } + if (!canUpgradePackage(pkg)) { + canUpgrade = false; + } + final String pyPackageName = pkg.getName(); + final String availableVersion = (String)myPackagesTable.getValueAt(index, 2); + if (!upgradeAvailable) { + upgradeAvailable = PackageVersionComparator.VERSION_COMPARATOR.compare(pkg.getVersion(), availableVersion) < 0 && + !myCurrentlyInstalling.contains(pyPackageName); + } + if (!canUninstall && !canUpgrade) break; + } + } + } + myUninstallButton.setEnabled(canUninstall); + myUpgradeButton.setEnabled(upgradeAvailable && canUpgrade); + } + }, ModalityState.any()); + } + + protected boolean canUninstallPackage(InstalledPackage pyPackage) { + return true; + } + + protected boolean canUpgradePackage(InstalledPackage pyPackage) { + return true; + } + + private void addUninstallAction() { + myUninstallButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + final List packages = getSelectedPackages(); + final PackageManagementService selPackageManagementService = myPackageManagementService; + if (selPackageManagementService != null) { + PackageManagementService.Listener listener = new PackageManagementService.Listener() { + @Override + public void operationStarted(String packageName) { + myPackagesTable.setPaintBusy(true); + } + + @Override + public void operationFinished(String packageName, @Nullable String errorDescription) { + myPackagesTable.clearSelection(); + updatePackages(selPackageManagementService); + myPackagesTable.setPaintBusy(false); + if (errorDescription == null) { + if (packageName != null) { + myNotificationArea.showSuccess("Package '" + packageName + "' successfully uninstalled"); + } + else { + myNotificationArea.showSuccess("Packages successfully uninstalled"); + } + } + else { + myNotificationArea.showError("Uninstall packages failed. Details...", + "Uninstall Packages Failed", + "Uninstall packages failed.\n" + errorDescription); + } + } + }; + myPackageManagementService.uninstallPackages(packages, listener); + } + } + }); + } + + @NotNull + private List getSelectedPackages() { + final List results = new ArrayList(); + final int[] rows = myPackagesTable.getSelectedRows(); + for (int row : rows) { + final Object packageName = myPackagesTableModel.getValueAt(row, 0); + if (packageName instanceof InstalledPackage) { + results.add((InstalledPackage)packageName); + } + } + return results; + } + + public void updatePackages(@Nullable PackageManagementService packageManagementService) { + myPackageManagementService = packageManagementService; + myPackagesTable.clearSelection(); + myPackagesTableModel.getDataVector().clear(); + doUpdatePackages(packageManagementService); + } + + public void doUpdatePackages(final PackageManagementService packageManagementService) { + myPackagesTable.setPaintBusy(true); + final Application application = ApplicationManager.getApplication(); + application.executeOnPooledThread(new Runnable() { + @Override + public void run() { + Collection packages = Lists.newArrayList(); + if (packageManagementService != null) { + try { + packages = packageManagementService.getInstalledPackages(); + } + catch (IOException e) { + // do nothing, we already have an empty list + } + finally { + final Collection finalPackages = packages; + + final Map cache = buildNameToPackageMap(packageManagementService.getAllPackagesCached()); + if (cache.isEmpty()) { + refreshLatestVersions(); + } + application.invokeLater(new Runnable() { + @Override + public void run() { + if (packageManagementService == myPackageManagementService) { + myPackagesTableModel.getDataVector().clear(); + for (InstalledPackage pkg : finalPackages) { + RepoPackage repoPackage = cache.get(pkg.getName()); + final String version = repoPackage != null ? repoPackage.getLatestVersion() : null; + myPackagesTableModel + .addRow(new Object[]{pkg, pkg.getVersion(), version == null ? "" : version}); + } + if (!cache.isEmpty()) { + myPackagesTable.setPaintBusy(false); + } + } + } + }, ModalityState.any()); + } + } + } + }); + } + + private void refreshLatestVersions() { + final Application application = ApplicationManager.getApplication(); + application.executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + List packages = myPackageManagementService.reloadAllPackages(); + final Map packageMap = buildNameToPackageMap(packages); + application.invokeLater(new Runnable() { + @Override + public void run() { + for (int i = 0; i != myPackagesTableModel.getRowCount(); ++i) { + final InstalledPackage pyPackage = (InstalledPackage)myPackagesTableModel.getValueAt(i, 0); + final RepoPackage repoPackage = packageMap.get(pyPackage.getName()); + myPackagesTableModel.setValueAt(repoPackage == null ? null : repoPackage.getLatestVersion(), i, 2); + } + myPackagesTable.setPaintBusy(false); + } + }, ModalityState.stateForComponent(myPackagesTable)); + } + catch (IOException ignored) { + myPackagesTable.setPaintBusy(false); + } + } + }); + } + + private static Map buildNameToPackageMap(List packages) { + final Map packageMap = new HashMap(); + for (RepoPackage aPackage : packages) { + packageMap.put(aPackage.getName(), aPackage); + } + return packageMap; + } + + private static class MyTableCellRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, + final boolean hasFocus, final int row, final int column) { + final JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + final String version = (String)table.getValueAt(row, 1); + final String availableVersion = (String)table.getValueAt(row, 2); + cell.setIcon(PackageVersionComparator.VERSION_COMPARATOR.compare(version, availableVersion) < 0 && column == 2 ? + AllIcons.Vcs.Arrow_right : null); + final Object pyPackage = table.getValueAt(row, 0); + if (pyPackage instanceof InstalledPackage) { + cell.setToolTipText(((InstalledPackage) pyPackage).getTooltipText()); + } + return cell; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.form b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.form new file mode 100644 index 000000000000..9337eed33403 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.form @@ -0,0 +1,181 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java new file mode 100644 index 000000000000..68bb7efcc6c2 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/ManagePackagesDialog.java @@ -0,0 +1,563 @@ +package com.intellij.webcore.packaging; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.*; +import com.intellij.ui.components.JBList; +import com.intellij.util.CatchingConsumer; +import com.intellij.util.Function; +import com.intellij.util.ObjectUtils; +import com.intellij.util.ui.PlatformColors; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.UiNotifyConnector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLFrameHyperlinkEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.List; + +/** + * User: catherine + *

+ * UI for installing python packages + */ +public class ManagePackagesDialog extends DialogWrapper { + private static final Logger LOG = Logger.getInstance(ManagePackagesDialog.class); + + @NotNull private final Project myProject; + private final PackageManagementService myController; + + private JPanel myFilter; + private JPanel myMainPanel; + private JEditorPane myDescriptionTextArea; + private JBList myPackages; + private JButton myInstallButton; + private JCheckBox myOptionsCheckBox; + private JTextField myOptionsField; + private JCheckBox myInstallToUser; + private JComboBox myVersionComboBox; + private JCheckBox myVersionCheckBox; + private JButton myManageButton; + private final PackagesNotificationPanel myNotificationArea; + private JSplitPane mySplitPane; + private JPanel myNotificationsAreaPlaceholder; + private PackagesModel myPackagesModel; + private String mySelectedPackageName; + private final Set myInstalledPackages; + @Nullable private final PackageManagementService.Listener myPackageListener; + + private Set myCurrentlyInstalling = new HashSet(); + protected final ListSpeedSearch myListSpeedSearch; + + public ManagePackagesDialog(@NotNull Project project, final PackageManagementService packageManagementService, + @Nullable final PackageManagementService.Listener packageListener) { + super(project, true); + myProject = project; + myController = packageManagementService; + + myPackageListener = packageListener; + init(); + setTitle("Available Packages"); + myPackages = new JBList(); + myNotificationArea = new PackagesNotificationPanel(project); + myNotificationsAreaPlaceholder.add(myNotificationArea.getComponent(), BorderLayout.CENTER); + + final AnActionButton reloadButton = new AnActionButton("Reload List of Packages", AllIcons.Actions.Refresh) { + @Override + public void actionPerformed(AnActionEvent e) { + myPackages.setPaintBusy(true); + final Application application = ApplicationManager.getApplication(); + application.executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + myController.reloadAllPackages(); + myPackages.setPaintBusy(false); + } + catch (final IOException e) { + application.invokeLater(new Runnable() { + @Override + public void run() { + //noinspection DialogTitleCapitalization + Messages.showErrorDialog("Error updating package list: " + e.getMessage(), "Reload List of Packages"); + myPackages.setPaintBusy(false); + } + }, ModalityState.any()); + } + } + }); + } + }; + myListSpeedSearch = new ListSpeedSearch(myPackages, new Function() { + @Override + public String fun(Object o) { + if (o instanceof RepoPackage) + return ((RepoPackage)o).getName(); + return ""; + } + }); + JPanel packagesPanel = ToolbarDecorator.createDecorator(myPackages) + .disableAddAction() + .disableUpDownActions() + .disableRemoveAction() + .addExtraAction(reloadButton) + .createPanel(); + packagesPanel.setPreferredSize(new Dimension(400, -1)); + packagesPanel.setMinimumSize(new Dimension(100, -1)); + myPackages.setFixedCellWidth(0); + myPackages.setFixedCellHeight(22); + myPackages.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + mySplitPane.setLeftComponent(packagesPanel); + + myPackages.addListSelectionListener(new MyPackageSelectionListener()); + myInstallToUser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + myController.installToUserChanged(myInstallToUser.isSelected()); + } + }); + myOptionsCheckBox.setEnabled(false); + myVersionCheckBox.setEnabled(false); + myVersionCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + myVersionComboBox.setEnabled(myVersionCheckBox.isSelected()); + } + }); + + UiNotifyConnector.doWhenFirstShown(myPackages, new Runnable() { + @Override + public void run() { + initModel(); + } + }); + myOptionsCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + myOptionsField.setEnabled(myOptionsCheckBox.isSelected()); + } + }); + myInstallButton.setEnabled(false); + myDescriptionTextArea.addHyperlinkListener(new MyHyperlinkListener()); + addInstallAction(); + myInstalledPackages = new HashSet(); + updateInstalledPackages(); + addManageAction(); + myPackages.setCellRenderer(new MyTableRenderer()); + + if (myController.canInstallToUser()) { + myInstallToUser.setVisible(true); + myInstallToUser.setSelected(myController.isInstallToUserSelected()); + myInstallToUser.setText(myController.getInstallToUserText()); + } + else { + myInstallToUser.setVisible(false); + } + } + + public void selectPackage(@NotNull InstalledPackage pkg) { + mySelectedPackageName = pkg.getName(); + doSelectPackage(mySelectedPackageName); + } + + private void addManageAction() { + if (myController.getAllRepositories() != null) { + myManageButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + ManageRepoDialog dialog = new ManageRepoDialog(myProject, myController); + dialog.show(); + } + }); + } + else { + myManageButton.setVisible(false); + } + } + + private void addInstallAction() { + myInstallButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + final Object pyPackage = myPackages.getSelectedValue(); + if (pyPackage instanceof RepoPackage) { + RepoPackage repoPackage = (RepoPackage)pyPackage; + + String extraOptions = null; + if (myOptionsCheckBox.isEnabled() && myOptionsCheckBox.isSelected()) { + extraOptions = myOptionsField.getText(); + } + + String version = null; + if (myVersionCheckBox.isEnabled() && myVersionCheckBox.isSelected()) { + version = (String) myVersionComboBox.getSelectedItem(); + } + + final PackageManagementService.Listener listener = new PackageManagementService.Listener() { + @Override + public void operationStarted(final String packageName) { + if (!ApplicationManager.getApplication().isDispatchThread()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + handleInstallationStarted(packageName); + } + }, ModalityState.stateForComponent(myMainPanel)); + } + else { + handleInstallationStarted(packageName); + } + } + + @Override + public void operationFinished(final String packageName, @Nullable final String errorDescription) { + if (!ApplicationManager.getApplication().isDispatchThread()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + handleInstallationFinished(packageName, errorDescription); + } + }, ModalityState.stateForComponent(myMainPanel)); + } + else { + handleInstallationFinished(packageName, errorDescription); + } + } + }; + myController.installPackage(repoPackage, version, false, extraOptions, listener, myInstallToUser.isSelected()); + myInstallButton.setEnabled(false); + } + } + }); + } + + private void handleInstallationStarted(String packageName) { + setDownloadStatus(true); + myCurrentlyInstalling.add(packageName); + if (myPackageListener != null) { + myPackageListener.operationStarted(packageName); + } + myPackages.repaint(); + } + + private void handleInstallationFinished(String packageName, String errorDescription) { + if (myPackageListener != null) { + myPackageListener.operationFinished(packageName, errorDescription); + } + setDownloadStatus(false); + myNotificationArea.showResult(packageName, errorDescription); + + updateInstalledPackages(); + + myCurrentlyInstalling.remove(packageName); + myPackages.repaint(); + } + + private void updateInstalledPackages() { + try { + Collection installedPackages = myController.getInstalledPackages(); + myInstalledPackages.clear(); + for (InstalledPackage pkg : installedPackages) { + myInstalledPackages.add(pkg.getName()); + } + } + catch(IOException e) { + LOG.info("Error updating list of installed packages:" + e); + } + } + + public void initModel() { + setDownloadStatus(true); + final Application application = ApplicationManager.getApplication(); + application.executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + myPackagesModel = new PackagesModel(myController.getAllPackages()); + + application.invokeLater(new Runnable() { + @Override + public void run() { + myPackages.setModel(myPackagesModel); + doSelectPackage(mySelectedPackageName); + setDownloadStatus(false); + } + }, ModalityState.any()); + } + catch (final IOException e) { + application.invokeLater(new Runnable() { + @Override + public void run() { + Messages.showErrorDialog("Error loading package list:" + e.getMessage(), "Packages"); + setDownloadStatus(false); + } + }, ModalityState.any()); + } + } + }); + } + + private void doSelectPackage(@Nullable String packageName) { + PackagesModel packagesModel = ObjectUtils.tryCast(myPackages.getModel(), PackagesModel.class); + if (packageName == null || packagesModel == null) { + return; + } + for (int i = 0; i < packagesModel.getSize(); i++) { + RepoPackage repoPackage = packagesModel.getElementAt(i); + if (packageName.equals(repoPackage.getName())) { + myPackages.setSelectedIndex(i); + myPackages.ensureIndexIsVisible(i); + break; + } + } + } + + protected void setDownloadStatus(boolean status) { + myPackages.setPaintBusy(status); + } + + @Override + protected JComponent createCenterPanel() { + return myMainPanel; + } + + private void createUIComponents() { + myFilter = new MyPackageFilter(); + } + + public class MyPackageFilter extends FilterComponent { + + public MyPackageFilter() { + super("PACKAGE_FILTER", 5); + } + + public void filter() { + if (myPackagesModel != null) + myPackagesModel.filter(getFilter()); + } + } + + private class PackagesModel extends CollectionListModel { + protected final List myFilteredOut = new ArrayList(); + protected List myView = new ArrayList(); + + public PackagesModel(List packages) { + super(packages); + myView = packages; + } + + public void add(String urlResource, String element) { + super.add(new RepoPackage(element, urlResource)); + } + + protected void filter(final String filter) { + final Collection toProcess = toProcess(); + + toProcess.addAll(myFilteredOut); + myFilteredOut.clear(); + + final ArrayList filtered = new ArrayList(); + + for (RepoPackage repoPackage : toProcess) { + if (StringUtil.containsIgnoreCase(repoPackage.getName(), filter)) { + filtered.add(repoPackage); + } + else { + myFilteredOut.add(repoPackage); + } + } + filter(filtered); + } + + public void filter(List filtered){ + myView.clear(); + myPackages.clearSelection(); + for (RepoPackage repoPackage : filtered) { + myView.add(repoPackage); + } + Collections.sort(myView); + fireContentsChanged(this, 0, myView.size()); + } + + @Override + public RepoPackage getElementAt(int index) { + return myView.get(index); + } + + protected ArrayList toProcess() { + return new ArrayList(myView); + } + + @Override + public int getSize() { + return myView.size(); + } + } + + @Nullable + public JComponent getPreferredFocusedComponent() { + return myFilter; + } + + public static class MyHyperlinkListener implements HyperlinkListener { + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + JEditorPane pane = (JEditorPane)e.getSource(); + if (e instanceof HTMLFrameHyperlinkEvent) { + HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e; + HTMLDocument doc = (HTMLDocument)pane.getDocument(); + doc.processHTMLFrameHyperlinkEvent(evt); + } + else { + URL url = e.getURL(); + if (url != null) { + BrowserUtil.browse(url); + } + } + } + } + } + + public class MyPackageSelectionListener implements ListSelectionListener { + @Override + public void valueChanged(ListSelectionEvent event) { + myOptionsCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0); + myVersionCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0); + myOptionsCheckBox.setSelected(false); + myVersionCheckBox.setSelected(false); + myVersionComboBox.setEnabled(false); + myOptionsField.setEnabled(false); + myDescriptionTextArea.setText(""); + + setDownloadStatus(true); + final Object pyPackage = myPackages.getSelectedValue(); + if (pyPackage instanceof RepoPackage) { + final String packageName = ((RepoPackage)pyPackage).getName(); + myVersionComboBox.removeAllItems(); + if (myVersionCheckBox.isEnabled()) { + myController.fetchPackageVersions(packageName, new CatchingConsumer, Exception>() { + @Override + public void consume(final List releases) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myPackages.getSelectedValue() == pyPackage) { + myVersionComboBox.removeAllItems(); + for (String release : releases) { + myVersionComboBox.addItem(release); + } + } + } + }, ModalityState.any()); + } + + @Override + public void consume(Exception e) { + LOG.info("Error retrieving releases", e); + } + }); + } + myInstallButton.setEnabled(!myCurrentlyInstalling.contains(packageName)); + + myController.fetchPackageDetails(packageName, new CatchingConsumer() { + @Override + public void consume(final String details) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myPackages.getSelectedValue() == pyPackage) { + myDescriptionTextArea.setText(details); + } + } + }, ModalityState.any()); + } + + @Override + public void consume(Exception exception) { + LOG.info("Error retrieving package details", exception); + } + }); + } + else { + myInstallButton.setEnabled(false); + } + setDownloadStatus(false); + } + } + + @NotNull + protected Action[] createActions() { + return new Action[0]; + } + + private class MyTableRenderer extends DefaultListCellRenderer { + private JLabel myNameLabel = new JLabel(); + private JLabel myRepositoryLabel = new JLabel(); + private JPanel myPanel = new JPanel(new BorderLayout()); + + private MyTableRenderer() { + myPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 1)); + // setting border.left on myPanel doesn't prevent from myRepository being painted on left empty area + myNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0)); + + myRepositoryLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); + myPanel.add(myNameLabel, BorderLayout.WEST); + myPanel.add(myRepositoryLabel, BorderLayout.EAST); + myNameLabel.setOpaque(true); + } + + @Override + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + if (value instanceof RepoPackage) { + RepoPackage repoPackage = (RepoPackage) value; + String name = repoPackage.getName(); + if (myCurrentlyInstalling.contains(name)) { + name += " (installing)"; + } + myNameLabel.setText(name); + myRepositoryLabel.setText(repoPackage.getRepoUrl()); + Component orig = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + final Color fg = orig.getForeground(); + myNameLabel.setForeground(myInstalledPackages.contains(name) ? PlatformColors.BLUE : fg); + } + myRepositoryLabel.setForeground(Color.GRAY); + + final Color bg; + if (isSelected) { + bg = UIUtil.getListSelectionBackground(); + } + else { + bg = index % 2 == 1 ? UIUtil.getListBackground() : UIUtil.getDecoratedRowColor(); + } + myPanel.setBackground(bg); + myNameLabel.setBackground(bg); + myRepositoryLabel.setBackground(bg); + return myPanel; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.form b/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.form new file mode 100644 index 000000000000..b60a46a1d970 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.form @@ -0,0 +1,11 @@ + +

+ + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.java b/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.java new file mode 100644 index 000000000000..0ce4aa57e6f5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/ManageRepoDialog.java @@ -0,0 +1,116 @@ +package com.intellij.webcore.packaging; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.InputValidator; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.AnActionButtonRunnable; +import com.intellij.ui.AnActionButtonUpdater; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.components.JBList; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; + +public class ManageRepoDialog extends DialogWrapper { + private JPanel myMainPanel; + private JBList myList; + private boolean myEnabled; + + public ManageRepoDialog(Project project, final PackageManagementService controller) { + super(project, false); + init(); + setTitle("Manage Repositories"); + final DefaultListModel repoModel = new DefaultListModel(); + for(String repoUrl: controller.getAllRepositories()) { + repoModel.addElement(repoUrl); + } + myList = new JBList(); + myList.setModel(repoModel); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + myList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent event) { + final Object selected = myList.getSelectedValue(); + myEnabled = controller.canModifyRepository((String) selected); + } + }); + + final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myList).disableUpDownActions(); + decorator.setAddActionName("Add repository"); + decorator.setRemoveActionName("Remove repository from list"); + decorator.setEditActionName("Edit repository URL"); + + decorator.setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + String url = Messages.showInputDialog("Please input repository URL", "Repository URL", null); + if (!repoModel.contains(url) && !StringUtil.isEmptyOrSpaces(url)) { + repoModel.addElement(url); + controller.addRepository(url); + } + } + }); + decorator.setEditAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final String oldValue = (String)myList.getSelectedValue(); + + String url = Messages.showInputDialog("Please edit repository URL", "Repository URL", null, oldValue, new InputValidator() { + @Override + public boolean checkInput(String inputString) { + return !repoModel.contains(inputString); + } + + @Override + public boolean canClose(String inputString) { + return true; + } + }); + if (!StringUtil.isEmptyOrSpaces(url) && !oldValue.equals(url)) { + repoModel.addElement(url); + repoModel.removeElement(oldValue); + controller.removeRepository(oldValue); + controller.addRepository(url); + } + } + }); + decorator.setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + String selected = (String)myList.getSelectedValue(); + controller.removeRepository(selected); + repoModel.removeElement(selected); + button.setEnabled(false); + } + }); + decorator.setRemoveActionUpdater(new AnActionButtonUpdater() { + @Override + public boolean isEnabled(AnActionEvent e) { + return myEnabled; + } + }); + decorator.setEditActionUpdater(new AnActionButtonUpdater() { + @Override + public boolean isEnabled(AnActionEvent e) { + return myEnabled; + } + }); + + final JPanel panel = decorator.createPanel(); + panel.setPreferredSize(new Dimension(800, 600)); + myMainPanel.add(panel); + + } + + @Override + protected JComponent createCenterPanel() { + return myMainPanel; + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java new file mode 100644 index 000000000000..2276bb71db31 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementService.java @@ -0,0 +1,139 @@ +package com.intellij.webcore.packaging; + +import com.intellij.util.CatchingConsumer; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author yole + */ +public abstract class PackageManagementService { + /** + * Returns the list of URLs for all configured package repositories. + * + * @return list of URLs, or null if the repository management is not supported by this package management service. + */ + @Nullable + public List getAllRepositories() { + return null; + } + + /** + * Checks if the user can change the URL of the specified repository or remove it from the list. + * + * @param repositoryUrl the URL to check + * @return true if can be modified, false otherwise. + */ + public boolean canModifyRepository(String repositoryUrl) { + return true; + } + + public void addRepository(String repositoryUrl) { + } + + public void removeRepository(String repositoryUrl) { + } + + /** + * Returns the list of all packages in all configured repositories. Called in a background thread + * and may initiate network connections. May return cached data. + * + * @return the list of all packages in all repositories + * @throws IOException + */ + public abstract List getAllPackages() throws IOException; + + /** + * Reloads the lists of packages for all configured repositories and returns the results. Called in a background thread + * and may initiate network connections. May not return cached data. + * + * @return the list of all packages in all repositories + * @throws IOException + */ + public abstract List reloadAllPackages() throws IOException; + + /** + * Returns the cached list of all packages in all configured repositories, or an empty list if there is no cached information available. + * + * @return the list of all packages or an empty list. + */ + public List getAllPackagesCached() { + return Collections.emptyList(); + } + + /** + * Returns true if the 'install to user' checkbox should be visible. + */ + public boolean canInstallToUser() { + return false; + } + + /** + * Returns the text of the 'install to user' checkbox. + * + * @return the text of the 'install to user' checkbox. + */ + public String getInstallToUserText() { + return ""; + } + + /** + * Returns true if the 'install to user' checkbox should be initially selected. + */ + public boolean isInstallToUserSelected() { + return false; + } + + /** + * Called when the 'install to user' checkbox is checked or unchecked. + */ + public void installToUserChanged(boolean newValue) { + } + + /** + * Returns the list of packages which are currently installed. + * + * @return the collection of currently installed packages. + */ + public abstract Collection getInstalledPackages() throws IOException; + + /** + * Installs the specified package. Called in the event dispatch thread; needs to take care of spawning a background task itself. + * + * @param repoPackage the package to install + * @param version the version selected by the user, or null if the checkbox to install a specific version is not checked + * @param forceUpgrade if true, the latest version of the package is installed even if there is already an installed version + * @param extraOptions additional options entered by the user + * @param listener the listener that must be called to publish information about the installation progress + * @param installToUser the state of the "install to user" checkbox (ignore if not applicable) + */ + public abstract void installPackage(RepoPackage repoPackage, @Nullable String version, boolean forceUpgrade, + @Nullable String extraOptions, Listener listener, boolean installToUser); + + public abstract void uninstallPackages(List installedPackages, Listener listener); + + public abstract void fetchPackageVersions(String packageName, CatchingConsumer, Exception> consumer); + + public abstract void fetchPackageDetails(String packageName, CatchingConsumer consumer); + + public interface Listener { + /** + * Fired when the installation of the specified package is started. + * + * @param packageName the name of the package being installed. + */ + void operationStarted(String packageName); + + /** + * Fired when the installation of the specified package has been completed (successfully or unsuccessfully). + * + * @param packageName the name of the installed package. + * @param errorDescription null if the package has been installed successfully, error message otherwise. + */ + void operationFinished(String packageName, @Nullable String errorDescription); + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementServiceEx.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementServiceEx.java new file mode 100644 index 000000000000..8b53fe6b2747 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageManagementServiceEx.java @@ -0,0 +1,15 @@ +package com.intellij.webcore.packaging; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Sergey Simonchik + */ +public abstract class PackageManagementServiceEx extends PackageManagementService { + + public abstract void updatePackage(@NotNull InstalledPackage installedPackage, + @Nullable String version, + @NotNull Listener listener); + +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackageVersionComparator.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageVersionComparator.java new file mode 100644 index 000000000000..8231bf467c54 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackageVersionComparator.java @@ -0,0 +1,83 @@ +package com.intellij.webcore.packaging; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PackageVersionComparator implements Comparator { + public static final Comparator VERSION_COMPARATOR = new PackageVersionComparator(); + + @Override + public int compare(String version1, String version2) { + final List vs1 = parse(version1); + final List vs2 = parse(version2); + int result = 0; + for (int i = 0; i < vs1.size() && i < vs2.size(); i++) { + result = vs1.get(i).compareTo(vs2.get(i)); + if (result != 0) { + break; + } + } + if (result == 0) { + return vs1.size() - vs2.size(); + } + return result; + } + + @Nullable + private static String replace(@NotNull String s) { + final Map sub = ImmutableMap.of("pre", "c", + "preview", "c", + "rc", "c", + "dev", "@"); + final String tmp = sub.get(s); + if (tmp != null) { + s = tmp; + } + if (s.equals(".") || s.equals("-")) { + return null; + } + if (s.matches("[0-9]+")) { + try { + final long value = Long.parseLong(s); + return String.format("%08d", value); + } + catch (NumberFormatException e) { + return s; + } + } + return "*" + s; + } + + @NotNull + private List parse(@Nullable String s) { + // Version parsing from pkg_resources ensures that all the "pre", "alpha", "rc", etc. are sorted correctly + if (s == null) { + return Collections.emptyList(); + } + final Pattern COMPONENT_RE = Pattern.compile("\\d+|[a-z]+|\\.|-|.+"); + final List results = new ArrayList(); + final Matcher matcher = COMPONENT_RE.matcher(s); + while (matcher.find()) { + final String component = replace(matcher.group()); + if (component == null) { + continue; + } + results.add(component); + } + for (int i = results.size() - 1; i > 0; i--) { + if ("00000000".equals(results.get(i))) { + results.remove(i); + } + else { + break; + } + } + results.add("*final"); + return results; + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java new file mode 100644 index 000000000000..679674523d8f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/PackagesNotificationPanel.java @@ -0,0 +1,128 @@ +package com.intellij.webcore.packaging; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.HyperlinkAdapter; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author yole + */ +public class PackagesNotificationPanel { + private final JEditorPane myEditorPane = new JEditorPane(); + private final Project myProject; + private final Map myLinkHandlers = new HashMap(); + private String myErrorTitle; + private String myErrorDescription; + + public PackagesNotificationPanel(Project project) { + myProject = project; + myEditorPane.setBackground(UIManager.getColor("ArrowButton.background")); + myEditorPane.setContentType("text/html"); + myEditorPane.setEditable(false); + myEditorPane.addHyperlinkListener(new HyperlinkAdapter() { + @Override + protected void hyperlinkActivated(HyperlinkEvent e) { + final Runnable handler = myLinkHandlers.get(e.getDescription()); + if (handler != null) { + handler.run(); + } + else if (myErrorTitle != null && myErrorDescription != null) { + showError(myProject, myErrorTitle, myErrorDescription); + } + } + }); + } + + public static void showError(@NotNull Project project, @NotNull String title, @NotNull String description) { + doShowError(title, description, new DialogBuilder(project)); + } + + public static void showError(@NotNull Component owner, @NotNull String title, @NotNull String description) { + doShowError(title, description, new DialogBuilder(owner)); + } + + private static void doShowError(String title, String description, DialogBuilder builder) { + builder.setTitle(title); + final JTextArea textArea = new JTextArea(); + textArea.setEditable(false); + textArea.setText(description); + textArea.setWrapStyleWord(false); + textArea.setLineWrap(true); + final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(textArea); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + final JPanel panel = new JPanel(new BorderLayout(10, 0)); + panel.setPreferredSize(new Dimension(600, 400)); + panel.add(scrollPane, BorderLayout.CENTER); + panel.add(new JBLabel("Details:", Messages.getErrorIcon(), SwingConstants.LEFT), BorderLayout.NORTH); + builder.setCenterPanel(panel); + builder.setButtonsAlignment(SwingConstants.CENTER); + builder.addOkAction(); + builder.show(); + } + + public void showResult(String packageName, @Nullable String errorDescription) { + if (StringUtil.isEmpty(errorDescription)) { + showSuccess("Package successfully installed."); + } + else { + String title = "Install packages failed"; + final String firstLine = title + ": Error occurred when installing package " + packageName + ". "; + showError(firstLine + "Details...", + title, + firstLine + errorDescription); + } + } + + public void addLinkHandler(String key, Runnable handler) { + myLinkHandlers.put(key, handler); + } + + public JComponent getComponent() { + return myEditorPane; + } + + public void showSuccess(String text) { + showContent(text, MessageType.INFO.getPopupBackground()); + } + + private void showContent(String text, final Color background) { + myEditorPane.removeAll(); + myEditorPane.setText(UIUtil.toHtml(text)); + myEditorPane.setBackground(background); + myEditorPane.setVisible(true); + myErrorTitle = null; + myErrorDescription = null; + } + + public void showError(String text, final String detailsTitle, final String detailsDescription) { + showContent(text, MessageType.ERROR.getPopupBackground()); + myErrorTitle = detailsTitle; + myErrorDescription = detailsDescription; + } + + public void showWarning(String text) { + showContent(text, MessageType.WARNING.getPopupBackground()); + } + + public void hide() { + myEditorPane.setVisible(false); + } + + public boolean hasLinkHandler(String key) { + return myLinkHandlers.containsKey(key); + } +} diff --git a/platform/lang-impl/src/com/intellij/webcore/packaging/RepoPackage.java b/platform/lang-impl/src/com/intellij/webcore/packaging/RepoPackage.java new file mode 100644 index 000000000000..76f7d386434a --- /dev/null +++ b/platform/lang-impl/src/com/intellij/webcore/packaging/RepoPackage.java @@ -0,0 +1,44 @@ +package com.intellij.webcore.packaging; + +import org.jetbrains.annotations.Nullable; + +/** + * User: catherine + */ +public class RepoPackage implements Comparable { + private final String myName; + private final String myRepoUrl; + @Nullable final String myLatestVersion; + + public RepoPackage(String name, String repoUrl) { + myName = name; + myRepoUrl = repoUrl; + myLatestVersion = null; + } + + public RepoPackage(String name, String repoUrl, @Nullable String latestVersion) { + myName = name; + myRepoUrl = repoUrl; + myLatestVersion = latestVersion; + } + + public String getName() { + return myName; + } + + public String getRepoUrl() { + return myRepoUrl; + } + + @Nullable + public String getLatestVersion() { + return myLatestVersion; + } + + @Override + public int compareTo(Object o) { + if (o instanceof RepoPackage) + return myName.compareTo(((RepoPackage)o).getName()); + return 0; + } +} \ No newline at end of file