2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.webcore.packaging;
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.plugins.PluginManagerMain;
20 import com.intellij.openapi.actionSystem.AnActionEvent;
21 import com.intellij.openapi.application.Application;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ModalityState;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.DialogWrapper;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.ui.*;
30 import com.intellij.ui.components.JBList;
31 import com.intellij.util.CatchingConsumer;
32 import com.intellij.util.Function;
33 import com.intellij.util.ObjectUtils;
34 import com.intellij.util.ui.JBUI;
35 import com.intellij.util.ui.PlatformColors;
36 import com.intellij.util.ui.UIUtil;
37 import com.intellij.util.ui.update.UiNotifyConnector;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
42 import javax.swing.event.ListSelectionEvent;
43 import javax.swing.event.ListSelectionListener;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.KeyAdapter;
48 import java.awt.event.KeyEvent;
49 import java.io.IOException;
51 import java.util.List;
56 * UI for installing python packages
58 public class ManagePackagesDialog extends DialogWrapper {
59 private static final Logger LOG = Logger.getInstance(ManagePackagesDialog.class);
61 @NotNull private final Project myProject;
62 private final PackageManagementService myController;
64 private JPanel myFilter;
65 private JPanel myMainPanel;
66 private JEditorPane myDescriptionTextArea;
67 private JBList myPackages;
68 private JButton myInstallButton;
69 private JCheckBox myOptionsCheckBox;
70 private JTextField myOptionsField;
71 private JCheckBox myInstallToUser;
72 private JComboBox myVersionComboBox;
73 private JCheckBox myVersionCheckBox;
74 private JButton myManageButton;
75 private final PackagesNotificationPanel myNotificationArea;
76 private JSplitPane mySplitPane;
77 private JPanel myNotificationsAreaPlaceholder;
78 private PackagesModel myPackagesModel;
79 private String mySelectedPackageName;
80 private final Set<String> myInstalledPackages;
81 @Nullable private final PackageManagementService.Listener myPackageListener;
83 private Set<String> myCurrentlyInstalling = new HashSet<String>();
84 protected final ListSpeedSearch myListSpeedSearch;
86 public ManagePackagesDialog(@NotNull Project project, final PackageManagementService packageManagementService,
87 @Nullable final PackageManagementService.Listener packageListener) {
90 myController = packageManagementService;
92 myPackageListener = packageListener;
94 setTitle("Available Packages");
95 myPackages = new JBList();
96 myNotificationArea = new PackagesNotificationPanel();
97 myNotificationsAreaPlaceholder.add(myNotificationArea.getComponent(), BorderLayout.CENTER);
99 final AnActionButton reloadButton = new AnActionButton("Reload List of Packages", AllIcons.Actions.Refresh) {
101 public void actionPerformed(AnActionEvent e) {
102 myPackages.setPaintBusy(true);
103 final Application application = ApplicationManager.getApplication();
104 application.executeOnPooledThread(new Runnable() {
108 myController.reloadAllPackages();
110 myPackages.setPaintBusy(false);
112 catch (final IOException e) {
113 application.invokeLater(new Runnable() {
116 //noinspection DialogTitleCapitalization
117 Messages.showErrorDialog(myMainPanel, "Error updating package list: " + e.getMessage(), "Reload List of Packages");
118 myPackages.setPaintBusy(false);
120 }, ModalityState.any());
126 myListSpeedSearch = new ListSpeedSearch(myPackages, new Function<Object, String>() {
128 public String fun(Object o) {
129 if (o instanceof RepoPackage)
130 return ((RepoPackage)o).getName();
134 JPanel packagesPanel = ToolbarDecorator.createDecorator(myPackages)
136 .disableUpDownActions()
137 .disableRemoveAction()
138 .addExtraAction(reloadButton)
140 packagesPanel.setPreferredSize(new Dimension(JBUI.scale(400), -1));
141 packagesPanel.setMinimumSize(new Dimension(JBUI.scale(100), -1));
142 myPackages.setFixedCellWidth(0);
143 myPackages.setFixedCellHeight(JBUI.scale(22));
144 myPackages.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
145 mySplitPane.setLeftComponent(packagesPanel);
147 myPackages.addListSelectionListener(new MyPackageSelectionListener());
148 myInstallToUser.addActionListener(new ActionListener() {
150 public void actionPerformed(ActionEvent event) {
151 myController.installToUserChanged(myInstallToUser.isSelected());
154 myOptionsCheckBox.setEnabled(false);
155 myVersionCheckBox.setEnabled(false);
156 myVersionCheckBox.addActionListener(new ActionListener() {
158 public void actionPerformed(ActionEvent event) {
159 myVersionComboBox.setEnabled(myVersionCheckBox.isSelected());
163 UiNotifyConnector.doWhenFirstShown(myPackages, new Runnable() {
169 myOptionsCheckBox.addActionListener(new ActionListener() {
171 public void actionPerformed(ActionEvent event) {
172 myOptionsField.setEnabled(myOptionsCheckBox.isSelected());
175 myInstallButton.setEnabled(false);
176 myDescriptionTextArea.addHyperlinkListener(new PluginManagerMain.MyHyperlinkListener());
178 myInstalledPackages = new HashSet<String>();
179 updateInstalledPackages();
181 myPackages.setCellRenderer(new MyTableRenderer());
183 if (myController.canInstallToUser()) {
184 myInstallToUser.setVisible(true);
185 myInstallToUser.setSelected(myController.isInstallToUserSelected());
186 myInstallToUser.setText(myController.getInstallToUserText());
189 myInstallToUser.setVisible(false);
191 myMainPanel.setPreferredSize(new Dimension(JBUI.scale(900), JBUI.scale(700)));
194 public void selectPackage(@NotNull InstalledPackage pkg) {
195 mySelectedPackageName = pkg.getName();
196 doSelectPackage(mySelectedPackageName);
199 private void addManageAction() {
200 if (myController.getAllRepositories() != null) {
201 myManageButton.addActionListener(new ActionListener() {
203 public void actionPerformed(ActionEvent event) {
204 ManageRepoDialog dialog = new ManageRepoDialog(myProject, myController);
210 myManageButton.setVisible(false);
214 private void addInstallAction() {
215 myInstallButton.addActionListener(new ActionListener() {
217 public void actionPerformed(ActionEvent event) {
218 final Object pyPackage = myPackages.getSelectedValue();
219 if (pyPackage instanceof RepoPackage) {
220 RepoPackage repoPackage = (RepoPackage)pyPackage;
222 String extraOptions = null;
223 if (myOptionsCheckBox.isEnabled() && myOptionsCheckBox.isSelected()) {
224 extraOptions = myOptionsField.getText();
227 String version = null;
228 if (myVersionCheckBox.isEnabled() && myVersionCheckBox.isSelected()) {
229 version = (String) myVersionComboBox.getSelectedItem();
232 final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
234 public void operationStarted(final String packageName) {
235 if (!ApplicationManager.getApplication().isDispatchThread()) {
236 ApplicationManager.getApplication().invokeLater(new Runnable() {
239 handleInstallationStarted(packageName);
241 }, ModalityState.stateForComponent(myMainPanel));
244 handleInstallationStarted(packageName);
249 public void operationFinished(final String packageName,
250 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
251 if (!ApplicationManager.getApplication().isDispatchThread()) {
252 ApplicationManager.getApplication().invokeLater(new Runnable() {
255 handleInstallationFinished(packageName, errorDescription);
257 }, ModalityState.stateForComponent(myMainPanel));
260 handleInstallationFinished(packageName, errorDescription);
264 myController.installPackage(repoPackage, version, false, extraOptions, listener, myInstallToUser.isSelected());
265 myInstallButton.setEnabled(false);
271 private void handleInstallationStarted(String packageName) {
272 setDownloadStatus(true);
273 myCurrentlyInstalling.add(packageName);
274 if (myPackageListener != null) {
275 myPackageListener.operationStarted(packageName);
277 myPackages.repaint();
280 private void handleInstallationFinished(String packageName, PackageManagementService.ErrorDescription errorDescription) {
281 if (myPackageListener != null) {
282 myPackageListener.operationFinished(packageName, errorDescription);
284 setDownloadStatus(false);
285 myNotificationArea.showResult(packageName, errorDescription);
287 updateInstalledPackages();
289 myCurrentlyInstalling.remove(packageName);
290 myPackages.repaint();
293 private void updateInstalledPackages() {
294 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
298 final Collection<InstalledPackage> installedPackages = myController.getInstalledPackages();
299 UIUtil.invokeLaterIfNeeded(new Runnable() {
302 myInstalledPackages.clear();
303 for (InstalledPackage pkg : installedPackages) {
304 myInstalledPackages.add(pkg.getName());
309 catch(IOException e) {
310 LOG.info("Error updating list of installed packages:" + e);
316 public void initModel() {
317 setDownloadStatus(true);
318 final Application application = ApplicationManager.getApplication();
319 application.executeOnPooledThread(new Runnable() {
323 myPackagesModel = new PackagesModel(myController.getAllPackages());
325 application.invokeLater(new Runnable() {
328 myPackages.setModel(myPackagesModel);
329 ((MyPackageFilter)myFilter).filter();
330 doSelectPackage(mySelectedPackageName);
331 setDownloadStatus(false);
333 }, ModalityState.any());
335 catch (final IOException e) {
336 application.invokeLater(new Runnable() {
339 Messages.showErrorDialog(myMainPanel, "Error loading package list:" + e.getMessage(), "Packages");
340 setDownloadStatus(false);
342 }, ModalityState.any());
348 private void doSelectPackage(@Nullable String packageName) {
349 PackagesModel packagesModel = ObjectUtils.tryCast(myPackages.getModel(), PackagesModel.class);
350 if (packageName == null || packagesModel == null) {
353 for (int i = 0; i < packagesModel.getSize(); i++) {
354 RepoPackage repoPackage = packagesModel.getElementAt(i);
355 if (packageName.equals(repoPackage.getName())) {
356 myPackages.setSelectedIndex(i);
357 myPackages.ensureIndexIsVisible(i);
363 protected void setDownloadStatus(boolean status) {
364 myPackages.setPaintBusy(status);
368 protected JComponent createCenterPanel() {
372 private void createUIComponents() {
373 myFilter = new MyPackageFilter();
376 public void setOptionsText(@NotNull String optionsText) {
377 myOptionsField.setText(optionsText);
380 private class MyPackageFilter extends FilterComponent {
381 public MyPackageFilter() {
382 super("PACKAGE_FILTER", 5);
383 getTextEditor().addKeyListener(new KeyAdapter() {
384 public void keyPressed(final KeyEvent e) {
385 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
388 myPackages.requestFocus();
389 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
396 public void filter() {
397 if (myPackagesModel != null)
398 myPackagesModel.filter(getFilter());
402 private class PackagesModel extends CollectionListModel<RepoPackage> {
403 protected final List<RepoPackage> myFilteredOut = new ArrayList<RepoPackage>();
404 protected List<RepoPackage> myView = new ArrayList<RepoPackage>();
406 public PackagesModel(List<RepoPackage> packages) {
411 public void add(String urlResource, String element) {
412 super.add(new RepoPackage(element, urlResource));
415 protected void filter(final String filter) {
416 final Collection<RepoPackage> toProcess = toProcess();
418 toProcess.addAll(myFilteredOut);
419 myFilteredOut.clear();
421 final ArrayList<RepoPackage> filtered = new ArrayList<RepoPackage>();
423 RepoPackage toSelect = null;
424 for (RepoPackage repoPackage : toProcess) {
425 final String packageName = repoPackage.getName();
426 if (StringUtil.containsIgnoreCase(packageName, filter)) {
427 filtered.add(repoPackage);
430 myFilteredOut.add(repoPackage);
432 if (StringUtil.equalsIgnoreCase(packageName, filter)) toSelect = repoPackage;
434 filter(filtered, toSelect);
437 public void filter(List<RepoPackage> filtered, @Nullable final RepoPackage toSelect){
439 myPackages.clearSelection();
440 for (RepoPackage repoPackage : filtered) {
441 myView.add(repoPackage);
443 if (toSelect != null)
444 myPackages.setSelectedValue(toSelect, true);
445 Collections.sort(myView);
446 fireContentsChanged(this, 0, myView.size());
450 public RepoPackage getElementAt(int index) {
451 return myView.get(index);
454 protected ArrayList<RepoPackage> toProcess() {
455 return new ArrayList<RepoPackage>(myView);
459 public int getSize() {
460 return myView.size();
465 public JComponent getPreferredFocusedComponent() {
469 private class MyPackageSelectionListener implements ListSelectionListener {
471 public void valueChanged(ListSelectionEvent event) {
472 myOptionsCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
473 myVersionCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
474 myOptionsCheckBox.setSelected(false);
475 myVersionCheckBox.setSelected(false);
476 myVersionComboBox.setEnabled(false);
477 myOptionsField.setEnabled(false);
478 myDescriptionTextArea.setText("<html><body style='text-align: center;padding-top:20px;'>Loading...</body></html>");
479 final Object pyPackage = myPackages.getSelectedValue();
480 if (pyPackage instanceof RepoPackage) {
481 final String packageName = ((RepoPackage)pyPackage).getName();
482 mySelectedPackageName = packageName;
483 myVersionComboBox.removeAllItems();
484 if (myVersionCheckBox.isEnabled()) {
485 myController.fetchPackageVersions(packageName, new CatchingConsumer<List<String>, Exception>() {
487 public void consume(final List<String> releases) {
488 ApplicationManager.getApplication().invokeLater(new Runnable() {
491 if (myPackages.getSelectedValue() == pyPackage) {
492 myVersionComboBox.removeAllItems();
493 for (String release : releases) {
494 myVersionComboBox.addItem(release);
498 }, ModalityState.any());
502 public void consume(Exception e) {
503 LOG.info("Error retrieving releases", e);
507 myInstallButton.setEnabled(!myCurrentlyInstalling.contains(packageName));
509 myController.fetchPackageDetails(packageName, new CatchingConsumer<String, Exception>() {
511 public void consume(final String details) {
512 UIUtil.invokeLaterIfNeeded(new Runnable() {
515 if (myPackages.getSelectedValue() == pyPackage) {
516 myDescriptionTextArea.setText(details);
517 myDescriptionTextArea.setCaretPosition(0);
519 do nothing, because other package gets selected
526 public void consume(Exception exception) {
527 UIUtil.invokeLaterIfNeeded(new Runnable() {
530 myDescriptionTextArea.setText("No information available");
533 LOG.info("Error retrieving package details", exception);
538 myInstallButton.setEnabled(false);
539 myDescriptionTextArea.setText("");
545 protected Action[] createActions() {
546 return new Action[0];
549 private class MyTableRenderer extends DefaultListCellRenderer {
550 private JLabel myNameLabel = new JLabel();
551 private JLabel myRepositoryLabel = new JLabel();
552 private JPanel myPanel = new JPanel(new BorderLayout());
554 private MyTableRenderer() {
555 myPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 1));
556 // setting border.left on myPanel doesn't prevent from myRepository being painted on left empty area
557 myNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
559 myRepositoryLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
560 myPanel.add(myNameLabel, BorderLayout.WEST);
561 myPanel.add(myRepositoryLabel, BorderLayout.EAST);
562 myNameLabel.setOpaque(true);
566 public Component getListCellRendererComponent(JList list,
570 boolean cellHasFocus) {
571 if (value instanceof RepoPackage) {
572 RepoPackage repoPackage = (RepoPackage) value;
573 String name = repoPackage.getName();
574 if (myCurrentlyInstalling.contains(name)) {
575 final String colorCode = UIUtil.isUnderDarcula() ? "589df6" : "0000FF";
576 name = "<html><body>" + repoPackage.getName() + " <font color=\"#" + colorCode + "\">(installing)</font></body></html>";
578 myNameLabel.setText(name);
579 myRepositoryLabel.setText(repoPackage.getRepoUrl());
580 Component orig = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
581 final Color fg = orig.getForeground();
582 myNameLabel.setForeground(myInstalledPackages.contains(name) ? PlatformColors.BLUE : fg);
584 myRepositoryLabel.setForeground(JBColor.GRAY);
588 bg = UIUtil.getListSelectionBackground();
591 bg = index % 2 == 1 ? UIUtil.getListBackground() : UIUtil.getDecoratedRowColor();
593 myPanel.setBackground(bg);
594 myNameLabel.setBackground(bg);
595 myRepositoryLabel.setBackground(bg);