EA-80238 - IAE: DialogWrapperPeerImpl.<init>
[idea/community.git] / platform / lang-impl / src / com / intellij / webcore / packaging / ManagePackagesDialog.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.webcore.packaging;
17
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;
40
41 import javax.swing.*;
42 import javax.swing.event.ListSelectionEvent;
43 import javax.swing.event.ListSelectionListener;
44 import java.awt.*;
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;
50 import java.util.*;
51 import java.util.List;
52
53 /**
54  * User: catherine
55  * <p/>
56  * UI for installing python packages
57  */
58 public class ManagePackagesDialog extends DialogWrapper {
59   private static final Logger LOG = Logger.getInstance(ManagePackagesDialog.class);
60
61   @NotNull private final Project myProject;
62   private final PackageManagementService myController;
63
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;
82
83   private Set<String> myCurrentlyInstalling = new HashSet<String>();
84   protected final ListSpeedSearch myListSpeedSearch;
85
86   public ManagePackagesDialog(@NotNull Project project, final PackageManagementService packageManagementService,
87                               @Nullable final PackageManagementService.Listener packageListener) {
88     super(project, true);
89     myProject = project;
90     myController = packageManagementService;
91
92     myPackageListener = packageListener;
93     init();
94     setTitle("Available Packages");
95     myPackages = new JBList();
96     myNotificationArea = new PackagesNotificationPanel();
97     myNotificationsAreaPlaceholder.add(myNotificationArea.getComponent(), BorderLayout.CENTER);
98
99     final AnActionButton reloadButton = new AnActionButton("Reload List of Packages", AllIcons.Actions.Refresh) {
100       @Override
101       public void actionPerformed(AnActionEvent e) {
102         myPackages.setPaintBusy(true);
103         final Application application = ApplicationManager.getApplication();
104         application.executeOnPooledThread(new Runnable() {
105           @Override
106           public void run() {
107             try {
108               myController.reloadAllPackages();
109               initModel();
110               myPackages.setPaintBusy(false);
111             }
112             catch (final IOException e) {
113               application.invokeLater(new Runnable() {
114                 @Override
115                 public void run() {
116                   //noinspection DialogTitleCapitalization
117                   Messages.showErrorDialog(myMainPanel, "Error updating package list: " + e.getMessage(), "Reload List of Packages");
118                   myPackages.setPaintBusy(false);
119                 }
120               }, ModalityState.any());
121             }
122           }
123         });
124       }
125     };
126     myListSpeedSearch = new ListSpeedSearch(myPackages, new Function<Object, String>() {
127       @Override
128       public String fun(Object o) {
129         if (o instanceof RepoPackage)
130           return ((RepoPackage)o).getName();
131         return "";
132       }
133     });
134     JPanel packagesPanel = ToolbarDecorator.createDecorator(myPackages)
135       .disableAddAction()
136       .disableUpDownActions()
137       .disableRemoveAction()
138       .addExtraAction(reloadButton)
139       .createPanel();
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);
146
147     myPackages.addListSelectionListener(new MyPackageSelectionListener());
148     myInstallToUser.addActionListener(new ActionListener() {
149       @Override
150       public void actionPerformed(ActionEvent event) {
151         myController.installToUserChanged(myInstallToUser.isSelected());
152       }
153     });
154     myOptionsCheckBox.setEnabled(false);
155     myVersionCheckBox.setEnabled(false);
156     myVersionCheckBox.addActionListener(new ActionListener() {
157       @Override
158       public void actionPerformed(ActionEvent event) {
159         myVersionComboBox.setEnabled(myVersionCheckBox.isSelected());
160       }
161     });
162
163     UiNotifyConnector.doWhenFirstShown(myPackages, new Runnable() {
164       @Override
165       public void run() {
166         initModel();
167       }
168     });
169     myOptionsCheckBox.addActionListener(new ActionListener() {
170       @Override
171       public void actionPerformed(ActionEvent event) {
172         myOptionsField.setEnabled(myOptionsCheckBox.isSelected());
173       }
174     });
175     myInstallButton.setEnabled(false);
176     myDescriptionTextArea.addHyperlinkListener(new PluginManagerMain.MyHyperlinkListener());
177     addInstallAction();
178     myInstalledPackages = new HashSet<String>();
179     updateInstalledPackages();
180     addManageAction();
181     myPackages.setCellRenderer(new MyTableRenderer());
182
183     if (myController.canInstallToUser()) {
184       myInstallToUser.setVisible(true);
185       myInstallToUser.setSelected(myController.isInstallToUserSelected());
186       myInstallToUser.setText(myController.getInstallToUserText());
187     }
188     else {
189       myInstallToUser.setVisible(false);
190     }
191     myMainPanel.setPreferredSize(new Dimension(JBUI.scale(900), JBUI.scale(700)));
192   }
193
194   public void selectPackage(@NotNull InstalledPackage pkg) {
195     mySelectedPackageName = pkg.getName();
196     doSelectPackage(mySelectedPackageName);
197   }
198
199   private void addManageAction() {
200     if (myController.getAllRepositories() != null) {
201       myManageButton.addActionListener(new ActionListener() {
202         @Override
203         public void actionPerformed(ActionEvent event) {
204           ManageRepoDialog dialog = new ManageRepoDialog(myProject, myController);
205           dialog.show();
206         }
207       });
208     }
209     else {
210       myManageButton.setVisible(false);
211     }
212   }
213
214   private void addInstallAction() {
215     myInstallButton.addActionListener(new ActionListener() {
216       @Override
217       public void actionPerformed(ActionEvent event) {
218         final Object pyPackage = myPackages.getSelectedValue();
219         if (pyPackage instanceof RepoPackage) {
220           RepoPackage repoPackage = (RepoPackage)pyPackage;
221
222           String extraOptions = null;
223           if (myOptionsCheckBox.isEnabled() && myOptionsCheckBox.isSelected()) {
224             extraOptions = myOptionsField.getText();
225           }
226
227           String version = null;
228           if (myVersionCheckBox.isEnabled() && myVersionCheckBox.isSelected()) {
229             version = (String) myVersionComboBox.getSelectedItem();
230           }
231
232           final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
233             @Override
234             public void operationStarted(final String packageName) {
235               if (!ApplicationManager.getApplication().isDispatchThread()) {
236                 ApplicationManager.getApplication().invokeLater(new Runnable() {
237                   @Override
238                   public void run() {
239                     handleInstallationStarted(packageName);
240                   }
241                 }, ModalityState.stateForComponent(myMainPanel));
242               }
243               else {
244                 handleInstallationStarted(packageName);
245               }
246             }
247
248             @Override
249             public void operationFinished(final String packageName,
250                                           @Nullable final PackageManagementService.ErrorDescription errorDescription) {
251               if (!ApplicationManager.getApplication().isDispatchThread()) {
252                 ApplicationManager.getApplication().invokeLater(new Runnable() {
253                   @Override
254                   public void run() {
255                     handleInstallationFinished(packageName, errorDescription);
256                   }
257                 }, ModalityState.stateForComponent(myMainPanel));
258               }
259               else {
260                 handleInstallationFinished(packageName, errorDescription);
261               }
262             }
263           };
264           myController.installPackage(repoPackage, version, false, extraOptions, listener, myInstallToUser.isSelected());
265           myInstallButton.setEnabled(false);
266         }
267       }
268     });
269   }
270
271   private void handleInstallationStarted(String packageName) {
272     setDownloadStatus(true);
273     myCurrentlyInstalling.add(packageName);
274     if (myPackageListener != null) {
275       myPackageListener.operationStarted(packageName);
276     }
277     myPackages.repaint();
278   }
279
280   private void handleInstallationFinished(String packageName, PackageManagementService.ErrorDescription errorDescription) {
281     if (myPackageListener != null) {
282       myPackageListener.operationFinished(packageName, errorDescription);
283     }
284     setDownloadStatus(false);
285     myNotificationArea.showResult(packageName, errorDescription);
286
287     updateInstalledPackages();
288
289     myCurrentlyInstalling.remove(packageName);
290     myPackages.repaint();
291   }
292
293   private void updateInstalledPackages() {
294     ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
295       @Override
296       public void run() {
297         try {
298           final Collection<InstalledPackage> installedPackages = myController.getInstalledPackages();
299           UIUtil.invokeLaterIfNeeded(new Runnable() {
300             @Override
301             public void run() {
302               myInstalledPackages.clear();
303               for (InstalledPackage pkg : installedPackages) {
304                 myInstalledPackages.add(pkg.getName());
305               }
306             }
307           });
308         }
309         catch(IOException e) {
310           LOG.info("Error updating list of installed packages:" + e);
311         }
312       }
313     });
314   }
315
316   public void initModel() {
317     setDownloadStatus(true);
318     final Application application = ApplicationManager.getApplication();
319     application.executeOnPooledThread(new Runnable() {
320       @Override
321       public void run() {
322         try {
323           myPackagesModel = new PackagesModel(myController.getAllPackages());
324
325           application.invokeLater(new Runnable() {
326             @Override
327             public void run() {
328               myPackages.setModel(myPackagesModel);
329               ((MyPackageFilter)myFilter).filter();
330               doSelectPackage(mySelectedPackageName);
331               setDownloadStatus(false);
332             }
333           }, ModalityState.any());
334         }
335         catch (final IOException e) {
336           application.invokeLater(new Runnable() {
337             @Override
338             public void run() {
339               if (myMainPanel.isShowing()) {
340                 Messages.showErrorDialog(myMainPanel, "Error loading package list:" + e.getMessage(), "Packages");
341               }
342               setDownloadStatus(false);
343             }
344           }, ModalityState.any());
345         }
346       }
347     });
348   }
349
350   private void doSelectPackage(@Nullable String packageName) {
351     PackagesModel packagesModel = ObjectUtils.tryCast(myPackages.getModel(), PackagesModel.class);
352     if (packageName == null || packagesModel == null) {
353       return;
354     }
355     for (int i = 0; i < packagesModel.getSize(); i++) {
356       RepoPackage repoPackage = packagesModel.getElementAt(i);
357       if (packageName.equals(repoPackage.getName())) {
358         myPackages.setSelectedIndex(i);
359         myPackages.ensureIndexIsVisible(i);
360         break;
361       }
362     }
363   }
364
365   protected void setDownloadStatus(boolean status) {
366     myPackages.setPaintBusy(status);
367   }
368
369   @Override
370   protected JComponent createCenterPanel() {
371     return myMainPanel;
372   }
373
374   private void createUIComponents() {
375     myFilter = new MyPackageFilter();
376   }
377
378   public void setOptionsText(@NotNull String optionsText) {
379     myOptionsField.setText(optionsText);
380   }
381
382   private class MyPackageFilter extends FilterComponent {
383     public MyPackageFilter() {
384       super("PACKAGE_FILTER", 5);
385       getTextEditor().addKeyListener(new KeyAdapter() {
386         public void keyPressed(final KeyEvent e) {
387           if (e.getKeyCode() == KeyEvent.VK_ENTER) {
388             e.consume();
389             filter();
390             myPackages.requestFocus();
391           } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
392             onEscape(e);
393           }
394         }
395       });
396     }
397
398     public void filter() {
399       if (myPackagesModel != null)
400         myPackagesModel.filter(getFilter());
401     }
402   }
403
404   private class PackagesModel extends CollectionListModel<RepoPackage> {
405     protected final List<RepoPackage> myFilteredOut = new ArrayList<RepoPackage>();
406     protected List<RepoPackage> myView = new ArrayList<RepoPackage>();
407
408     public PackagesModel(List<RepoPackage> packages) {
409       super(packages);
410       myView = packages;
411     }
412
413     public void add(String urlResource, String element) {
414       super.add(new RepoPackage(element, urlResource));
415     }
416
417     protected void filter(final String filter) {
418       final Collection<RepoPackage> toProcess = toProcess();
419
420       toProcess.addAll(myFilteredOut);
421       myFilteredOut.clear();
422
423       final ArrayList<RepoPackage> filtered = new ArrayList<RepoPackage>();
424
425       RepoPackage toSelect = null;
426       for (RepoPackage repoPackage : toProcess) {
427         final String packageName = repoPackage.getName();
428         if (StringUtil.containsIgnoreCase(packageName, filter)) {
429           filtered.add(repoPackage);
430         }
431         else {
432           myFilteredOut.add(repoPackage);
433         }
434         if (StringUtil.equalsIgnoreCase(packageName, filter)) toSelect = repoPackage;
435       }
436       filter(filtered, toSelect);
437     }
438
439     public void filter(List<RepoPackage> filtered, @Nullable final RepoPackage toSelect){
440       myView.clear();
441       myPackages.clearSelection();
442       for (RepoPackage repoPackage : filtered) {
443         myView.add(repoPackage);
444       }
445       if (toSelect != null)
446         myPackages.setSelectedValue(toSelect, true);
447       Collections.sort(myView);
448       fireContentsChanged(this, 0, myView.size());
449     }
450
451     @Override
452     public RepoPackage getElementAt(int index) {
453       return myView.get(index);
454     }
455
456     protected ArrayList<RepoPackage> toProcess() {
457       return new ArrayList<RepoPackage>(myView);
458     }
459
460     @Override
461     public int getSize() {
462       return myView.size();
463     }
464   }
465
466   @Nullable
467   public JComponent getPreferredFocusedComponent() {
468     return myFilter;
469   }
470
471   private class MyPackageSelectionListener implements ListSelectionListener {
472     @Override
473     public void valueChanged(ListSelectionEvent event) {
474       myOptionsCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
475       myVersionCheckBox.setEnabled(myPackages.getSelectedIndex() >= 0);
476       myOptionsCheckBox.setSelected(false);
477       myVersionCheckBox.setSelected(false);
478       myVersionComboBox.setEnabled(false);
479       myOptionsField.setEnabled(false);
480       myDescriptionTextArea.setText("<html><body style='text-align: center;padding-top:20px;'>Loading...</body></html>");
481       final Object pyPackage = myPackages.getSelectedValue();
482       if (pyPackage instanceof RepoPackage) {
483         final String packageName = ((RepoPackage)pyPackage).getName();
484         mySelectedPackageName = packageName;
485         myVersionComboBox.removeAllItems();
486         if (myVersionCheckBox.isEnabled()) {
487           myController.fetchPackageVersions(packageName, new CatchingConsumer<List<String>, Exception>() {
488             @Override
489             public void consume(final List<String> releases) {
490               ApplicationManager.getApplication().invokeLater(new Runnable() {
491                 @Override
492                 public void run() {
493                   if (myPackages.getSelectedValue() == pyPackage) {
494                     myVersionComboBox.removeAllItems();
495                     for (String release : releases) {
496                       myVersionComboBox.addItem(release);
497                     }
498                   }
499                 }
500               }, ModalityState.any());
501             }
502
503             @Override
504             public void consume(Exception e) {
505               LOG.info("Error retrieving releases", e);
506             }
507           });
508         }
509         myInstallButton.setEnabled(!myCurrentlyInstalling.contains(packageName));
510
511         myController.fetchPackageDetails(packageName, new CatchingConsumer<String, Exception>() {
512           @Override
513           public void consume(final String details) {
514             UIUtil.invokeLaterIfNeeded(new Runnable() {
515               @Override
516               public void run() {
517                 if (myPackages.getSelectedValue() == pyPackage) {
518                   myDescriptionTextArea.setText(details);
519                   myDescriptionTextArea.setCaretPosition(0);
520                 }/* else {
521                    do nothing, because other package gets selected
522                 }*/
523               }
524             });
525           }
526
527           @Override
528           public void consume(Exception exception) {
529             UIUtil.invokeLaterIfNeeded(new Runnable() {
530               @Override
531               public void run() {
532                 myDescriptionTextArea.setText("No information available");
533               }
534             });
535             LOG.info("Error retrieving package details", exception);
536           }
537         });
538       }
539       else {
540         myInstallButton.setEnabled(false);
541         myDescriptionTextArea.setText("");
542       }
543     }
544   }
545
546   @NotNull
547   protected Action[] createActions() {
548     return new Action[0];
549   }
550
551   private class MyTableRenderer extends DefaultListCellRenderer {
552     private JLabel myNameLabel = new JLabel();
553     private JLabel myRepositoryLabel = new JLabel();
554     private JPanel myPanel = new JPanel(new BorderLayout());
555
556     private MyTableRenderer() {
557       myPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 1));
558       // setting border.left on myPanel doesn't prevent from myRepository being painted on left empty area
559       myNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
560
561       myRepositoryLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
562       myPanel.add(myNameLabel, BorderLayout.WEST);
563       myPanel.add(myRepositoryLabel, BorderLayout.EAST);
564       myNameLabel.setOpaque(true);
565     }
566
567     @Override
568     public Component getListCellRendererComponent(JList list,
569                                                   Object value,
570                                                   int index,
571                                                   boolean isSelected,
572                                                   boolean cellHasFocus) {
573       if (value instanceof RepoPackage) {
574         RepoPackage repoPackage = (RepoPackage) value;
575         String name = repoPackage.getName();
576         if (myCurrentlyInstalling.contains(name)) {
577           final String colorCode = UIUtil.isUnderDarcula() ? "589df6" : "0000FF";
578           name = "<html><body>" + repoPackage.getName() + " <font color=\"#" + colorCode + "\">(installing)</font></body></html>";
579         }
580         myNameLabel.setText(name);
581         myRepositoryLabel.setText(repoPackage.getRepoUrl());
582         Component orig = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
583         final Color fg = orig.getForeground();
584         myNameLabel.setForeground(myInstalledPackages.contains(name) ? PlatformColors.BLUE : fg);
585       }
586       myRepositoryLabel.setForeground(JBColor.GRAY);
587
588       final Color bg;
589       if (isSelected) {
590         bg = UIUtil.getListSelectionBackground();
591       }
592       else {
593         bg = index % 2 == 1 ? UIUtil.getListBackground() : UIUtil.getDecoratedRowColor();
594       }
595       myPanel.setBackground(bg);
596       myNameLabel.setBackground(bg);
597       myRepositoryLabel.setBackground(bg);
598       return myPanel;
599     }
600   }
601 }