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.SwingHelper;
37 import com.intellij.util.ui.UIUtil;
38 import com.intellij.util.ui.update.UiNotifyConnector;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
43 import javax.swing.event.ListSelectionEvent;
44 import javax.swing.event.ListSelectionListener;
46 import java.awt.event.ActionEvent;
47 import java.awt.event.ActionListener;
48 import java.awt.event.KeyAdapter;
49 import java.awt.event.KeyEvent;
50 import java.io.IOException;
52 import java.util.List;
57 * UI for installing python packages
59 public class ManagePackagesDialog extends DialogWrapper {
60 private static final Logger LOG = Logger.getInstance(ManagePackagesDialog.class);
62 @NotNull private final Project myProject;
63 private final PackageManagementService myController;
65 private JPanel myFilter;
66 private JPanel myMainPanel;
67 private JEditorPane myDescriptionTextArea;
68 private JBList myPackages;
69 private JButton myInstallButton;
70 private JCheckBox myOptionsCheckBox;
71 private JTextField myOptionsField;
72 private JCheckBox myInstallToUser;
73 private JComboBox myVersionComboBox;
74 private JCheckBox myVersionCheckBox;
75 private JButton myManageButton;
76 private final PackagesNotificationPanel myNotificationArea;
77 private JSplitPane mySplitPane;
78 private JPanel myNotificationsAreaPlaceholder;
79 private PackagesModel myPackagesModel;
80 private String mySelectedPackageName;
81 private final Set<String> myInstalledPackages;
82 @Nullable private final PackageManagementService.Listener myPackageListener;
84 private Set<String> myCurrentlyInstalling = new HashSet<>();
85 protected final ListSpeedSearch myListSpeedSearch;
87 public ManagePackagesDialog(@NotNull Project project, final PackageManagementService packageManagementService,
88 @Nullable final PackageManagementService.Listener packageListener) {
91 myController = packageManagementService;
93 myPackageListener = packageListener;
95 setTitle("Available Packages");
96 myPackages = new JBList();
97 myNotificationArea = new PackagesNotificationPanel();
98 myNotificationsAreaPlaceholder.add(myNotificationArea.getComponent(), BorderLayout.CENTER);
100 final AnActionButton reloadButton = new AnActionButton("Reload List of Packages", AllIcons.Actions.Refresh) {
102 public void actionPerformed(AnActionEvent e) {
103 myPackages.setPaintBusy(true);
104 final Application application = ApplicationManager.getApplication();
105 application.executeOnPooledThread(() -> {
107 myController.reloadAllPackages();
109 myPackages.setPaintBusy(false);
111 catch (final IOException e1) {
112 application.invokeLater(() -> {
113 //noinspection DialogTitleCapitalization
114 Messages.showErrorDialog(myMainPanel, "Error updating package list: " + e1.getMessage(), "Reload List of Packages");
115 myPackages.setPaintBusy(false);
116 }, ModalityState.any());
121 myListSpeedSearch = new ListSpeedSearch(myPackages, new Function<Object, String>() {
123 public String fun(Object o) {
124 if (o instanceof RepoPackage)
125 return ((RepoPackage)o).getName();
129 JPanel packagesPanel = ToolbarDecorator.createDecorator(myPackages)
131 .disableUpDownActions()
132 .disableRemoveAction()
133 .addExtraAction(reloadButton)
135 packagesPanel.setPreferredSize(new Dimension(JBUI.scale(400), -1));
136 packagesPanel.setMinimumSize(new Dimension(JBUI.scale(100), -1));
137 myPackages.setFixedCellWidth(0);
138 myPackages.setFixedCellHeight(JBUI.scale(22));
139 myPackages.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
140 mySplitPane.setLeftComponent(packagesPanel);
142 myPackages.addListSelectionListener(new MyPackageSelectionListener());
143 myInstallToUser.addActionListener(new ActionListener() {
145 public void actionPerformed(ActionEvent event) {
146 myController.installToUserChanged(myInstallToUser.isSelected());
149 myOptionsCheckBox.setEnabled(false);
150 myVersionCheckBox.setEnabled(false);
151 myVersionCheckBox.addActionListener(new ActionListener() {
153 public void actionPerformed(ActionEvent event) {
154 myVersionComboBox.setEnabled(myVersionCheckBox.isSelected());
158 UiNotifyConnector.doWhenFirstShown(myPackages, () -> initModel());
159 myOptionsCheckBox.addActionListener(new ActionListener() {
161 public void actionPerformed(ActionEvent event) {
162 myOptionsField.setEnabled(myOptionsCheckBox.isSelected());
165 myInstallButton.setEnabled(false);
166 myDescriptionTextArea.addHyperlinkListener(new PluginManagerMain.MyHyperlinkListener());
168 myInstalledPackages = new HashSet<>();
169 updateInstalledPackages();
171 myPackages.setCellRenderer(new MyTableRenderer());
173 if (myController.canInstallToUser()) {
174 myInstallToUser.setVisible(true);
175 myInstallToUser.setSelected(myController.isInstallToUserSelected());
176 myInstallToUser.setText(myController.getInstallToUserText());
179 myInstallToUser.setVisible(false);
181 myMainPanel.setPreferredSize(new Dimension(JBUI.scale(900), JBUI.scale(700)));
184 public void selectPackage(@NotNull InstalledPackage pkg) {
185 mySelectedPackageName = pkg.getName();
186 doSelectPackage(mySelectedPackageName);
189 private void addManageAction() {
190 if (myController.getAllRepositories() != null) {
191 myManageButton.addActionListener(new ActionListener() {
193 public void actionPerformed(ActionEvent event) {
194 ManageRepoDialog dialog = new ManageRepoDialog(myProject, myController);
200 myManageButton.setVisible(false);
204 private void addInstallAction() {
205 myInstallButton.addActionListener(new ActionListener() {
207 public void actionPerformed(ActionEvent event) {
208 final Object pyPackage = myPackages.getSelectedValue();
209 if (pyPackage instanceof RepoPackage) {
210 RepoPackage repoPackage = (RepoPackage)pyPackage;
212 String extraOptions = null;
213 if (myOptionsCheckBox.isEnabled() && myOptionsCheckBox.isSelected()) {
214 extraOptions = myOptionsField.getText();
217 String version = null;
218 if (myVersionCheckBox.isEnabled() && myVersionCheckBox.isSelected()) {
219 version = (String) myVersionComboBox.getSelectedItem();
222 final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
224 public void operationStarted(final String packageName) {
225 if (!ApplicationManager.getApplication().isDispatchThread()) {
226 ApplicationManager.getApplication().invokeLater(() -> handleInstallationStarted(packageName), ModalityState.stateForComponent(myMainPanel));
229 handleInstallationStarted(packageName);
234 public void operationFinished(final String packageName,
235 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
236 if (!ApplicationManager.getApplication().isDispatchThread()) {
237 ApplicationManager.getApplication().invokeLater(() -> handleInstallationFinished(packageName, errorDescription), ModalityState.stateForComponent(myMainPanel));
240 handleInstallationFinished(packageName, errorDescription);
244 myController.installPackage(repoPackage, version, false, extraOptions, listener, myInstallToUser.isSelected());
245 myInstallButton.setEnabled(false);
251 private void handleInstallationStarted(String packageName) {
252 setDownloadStatus(true);
253 myCurrentlyInstalling.add(packageName);
254 if (myPackageListener != null) {
255 myPackageListener.operationStarted(packageName);
257 myPackages.repaint();
260 private void handleInstallationFinished(String packageName, PackageManagementService.ErrorDescription errorDescription) {
261 if (myPackageListener != null) {
262 myPackageListener.operationFinished(packageName, errorDescription);
264 setDownloadStatus(false);
265 myNotificationArea.showResult(packageName, errorDescription);
267 updateInstalledPackages();
269 myCurrentlyInstalling.remove(packageName);
270 myPackages.repaint();
273 private void updateInstalledPackages() {
274 ApplicationManager.getApplication().executeOnPooledThread(() -> {
276 final Collection<InstalledPackage> installedPackages = myController.getInstalledPackages();
277 UIUtil.invokeLaterIfNeeded(() -> {
278 myInstalledPackages.clear();
279 for (InstalledPackage pkg : installedPackages) {
280 myInstalledPackages.add(pkg.getName());
284 catch(IOException e) {
285 LOG.info("Error updating list of installed packages:" + e);
290 public void initModel() {
291 setDownloadStatus(true);
292 final Application application = ApplicationManager.getApplication();
293 application.executeOnPooledThread(() -> {
295 myPackagesModel = new PackagesModel(myController.getAllPackages());
297 application.invokeLater(() -> {
298 myPackages.setModel(myPackagesModel);
299 ((MyPackageFilter)myFilter).filter();
300 doSelectPackage(mySelectedPackageName);
301 setDownloadStatus(false);
302 }, ModalityState.any());
304 catch (final IOException e) {
305 application.invokeLater(() -> {
306 if (myMainPanel.isShowing()) {
307 Messages.showErrorDialog(myMainPanel, "Error loading package list:" + e.getMessage(), "Packages");
309 setDownloadStatus(false);
310 }, ModalityState.any());
315 private void doSelectPackage(@Nullable String packageName) {
316 PackagesModel packagesModel = ObjectUtils.tryCast(myPackages.getModel(), PackagesModel.class);
317 if (packageName == null || packagesModel == null) {
320 for (int i = 0; i < packagesModel.getSize(); i++) {
321 RepoPackage repoPackage = packagesModel.getElementAt(i);
322 if (packageName.equals(repoPackage.getName())) {
323 myPackages.setSelectedIndex(i);
324 myPackages.ensureIndexIsVisible(i);
330 protected void setDownloadStatus(boolean status) {
331 myPackages.setPaintBusy(status);
335 protected JComponent createCenterPanel() {
339 private void createUIComponents() {
340 myFilter = new MyPackageFilter();
341 myDescriptionTextArea = SwingHelper.createHtmlViewer(true, null, null, null);
344 public void setOptionsText(@NotNull String optionsText) {
345 myOptionsField.setText(optionsText);
348 private class MyPackageFilter extends FilterComponent {
349 public MyPackageFilter() {
350 super("PACKAGE_FILTER", 5);
351 getTextEditor().addKeyListener(new KeyAdapter() {
352 public void keyPressed(final KeyEvent e) {
353 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
356 myPackages.requestFocus();
357 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
364 public void filter() {
365 if (myPackagesModel != null)
366 myPackagesModel.filter(getFilter());
370 private class PackagesModel extends CollectionListModel<RepoPackage> {
371 protected final List<RepoPackage> myFilteredOut = new ArrayList<>();
372 protected List<RepoPackage> myView = new ArrayList<>();
374 public PackagesModel(List<RepoPackage> packages) {
379 public void add(String urlResource, String element) {
380 super.add(new RepoPackage(element, urlResource));
383 protected void filter(final String filter) {
384 final Collection<RepoPackage> toProcess = toProcess();
386 toProcess.addAll(myFilteredOut);
387 myFilteredOut.clear();
389 final ArrayList<RepoPackage> filtered = new ArrayList<>();
391 RepoPackage toSelect = null;
392 for (RepoPackage repoPackage : toProcess) {
393 final String packageName = repoPackage.getName();
394 if (StringUtil.containsIgnoreCase(packageName, filter)) {
395 filtered.add(repoPackage);
398 myFilteredOut.add(repoPackage);
400 if (StringUtil.equalsIgnoreCase(packageName, filter)) toSelect = repoPackage;
402 filter(filtered, toSelect);
405 public void filter(List<RepoPackage> filtered, @Nullable final RepoPackage toSelect){
407 myPackages.clearSelection();
408 for (RepoPackage repoPackage : filtered) {
409 myView.add(repoPackage);
411 if (toSelect != null)
412 myPackages.setSelectedValue(toSelect, true);
413 Collections.sort(myView);
414 fireContentsChanged(this, 0, myView.size());
418 public RepoPackage getElementAt(int index) {
419 return myView.get(index);
422 protected ArrayList<RepoPackage> toProcess() {
423 return new ArrayList<>(myView);
427 public int getSize() {
428 return myView.size();
433 public JComponent getPreferredFocusedComponent() {
437 private class MyPackageSelectionListener implements ListSelectionListener {
439 public void valueChanged(ListSelectionEvent event) {
440 myOptionsCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
441 myVersionCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
442 myOptionsCheckBox.setSelected(false);
443 myVersionCheckBox.setSelected(false);
444 myVersionComboBox.setEnabled(false);
445 myOptionsField.setEnabled(false);
446 myDescriptionTextArea.setText("<html><body style='text-align: center;padding-top:20px;'>Loading...</body></html>");
447 final Object pyPackage = myPackages.getSelectedValue();
448 if (pyPackage instanceof RepoPackage) {
449 final String packageName = ((RepoPackage)pyPackage).getName();
450 mySelectedPackageName = packageName;
451 myVersionComboBox.removeAllItems();
452 if (myVersionCheckBox.isEnabled()) {
453 myController.fetchPackageVersions(packageName, new CatchingConsumer<List<String>, Exception>() {
455 public void consume(final List<String> releases) {
456 ApplicationManager.getApplication().invokeLater(() -> {
457 if (myPackages.getSelectedValue() == pyPackage) {
458 myVersionComboBox.removeAllItems();
459 for (String release : releases) {
460 myVersionComboBox.addItem(release);
463 }, ModalityState.any());
467 public void consume(Exception e) {
468 LOG.info("Error retrieving releases", e);
472 myInstallButton.setEnabled(!myCurrentlyInstalling.contains(packageName));
474 myController.fetchPackageDetails(packageName, new CatchingConsumer<String, Exception>() {
476 public void consume(final String details) {
477 UIUtil.invokeLaterIfNeeded(() -> {
478 if (myPackages.getSelectedValue() == pyPackage) {
479 myDescriptionTextArea.setText(details);
480 myDescriptionTextArea.setCaretPosition(0);
482 do nothing, because other package gets selected
488 public void consume(Exception exception) {
489 UIUtil.invokeLaterIfNeeded(() -> myDescriptionTextArea.setText("No information available"));
490 LOG.info("Error retrieving package details", exception);
495 myInstallButton.setEnabled(false);
496 myDescriptionTextArea.setText("");
502 protected Action[] createActions() {
503 return new Action[0];
506 private class MyTableRenderer extends DefaultListCellRenderer {
507 private JLabel myNameLabel = new JLabel();
508 private JLabel myRepositoryLabel = new JLabel();
509 private JPanel myPanel = new JPanel(new BorderLayout());
511 private MyTableRenderer() {
512 myPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 1));
513 // setting border.left on myPanel doesn't prevent from myRepository being painted on left empty area
514 myNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
516 myRepositoryLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
517 myPanel.add(myNameLabel, BorderLayout.WEST);
518 myPanel.add(myRepositoryLabel, BorderLayout.EAST);
519 myNameLabel.setOpaque(true);
523 public Component getListCellRendererComponent(JList list,
527 boolean cellHasFocus) {
528 if (value instanceof RepoPackage) {
529 RepoPackage repoPackage = (RepoPackage) value;
530 String name = repoPackage.getName();
531 if (myCurrentlyInstalling.contains(name)) {
532 final String colorCode = UIUtil.isUnderDarcula() ? "589df6" : "0000FF";
533 name = "<html><body>" + repoPackage.getName() + " <font color=\"#" + colorCode + "\">(installing)</font></body></html>";
535 myNameLabel.setText(name);
536 myRepositoryLabel.setText(repoPackage.getRepoUrl());
537 Component orig = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
538 final Color fg = orig.getForeground();
539 myNameLabel.setForeground(myInstalledPackages.contains(name) ? PlatformColors.BLUE : fg);
541 myRepositoryLabel.setForeground(JBColor.GRAY);
545 bg = UIUtil.getListSelectionBackground();
548 bg = index % 2 == 1 ? UIUtil.getListBackground() : UIUtil.getDecoratedRowColor();
550 myPanel.setBackground(bg);
551 myNameLabel.setBackground(bg);
552 myRepositoryLabel.setBackground(bg);