AvailablePlugins.java
001 /*
002  * Copyright (c) 1995-2012, The University of Sheffield. See the file
003  * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  * Copyright (c) 2009, Ontotext AD.
005  
006  * This file is part of GATE (see http://gate.ac.uk/), and is free software,
007  * licenced under the GNU Library General Public License, Version 2, June 1991
008  * (in the distribution as file licence.html, and also available at
009  * http://gate.ac.uk/gate/licence.html).
010  
011  * PluginManagerUI.java
012  
013  * Valentin Tablan, 21-Jul-2004
014  
015  * $Id: PluginManagerUI.java 13565 2011-03-26 23:03:34Z johann_p $
016  */
017 
018 package gate.gui.creole.manager;
019 
020 import gate.Gate;
021 import gate.Gate.ResourceInfo;
022 import gate.gui.MainFrame;
023 import gate.resources.img.svg.AddIcon;
024 import gate.resources.img.svg.AvailableIcon;
025 import gate.resources.img.svg.GATEIcon;
026 import gate.resources.img.svg.InvalidIcon;
027 import gate.resources.img.svg.OpenFileIcon;
028 import gate.resources.img.svg.RemotePluginIcon;
029 import gate.resources.img.svg.RemoveIcon;
030 import gate.resources.img.svg.ResetIcon;
031 import gate.resources.img.svg.UserPluginIcon;
032 import gate.swing.CheckBoxTableCellRenderer;
033 import gate.swing.IconTableCellRenderer;
034 import gate.swing.XJFileChooser;
035 import gate.swing.XJTable;
036 import gate.util.GateRuntimeException;
037 
038 import java.awt.BorderLayout;
039 import java.awt.Component;
040 import java.awt.event.ActionEvent;
041 import java.awt.event.KeyAdapter;
042 import java.awt.event.KeyEvent;
043 import java.net.MalformedURLException;
044 import java.net.URL;
045 import java.text.Collator;
046 import java.util.ArrayList;
047 import java.util.Date;
048 import java.util.HashMap;
049 import java.util.HashSet;
050 import java.util.Iterator;
051 import java.util.List;
052 import java.util.Locale;
053 import java.util.Map;
054 import java.util.Set;
055 import java.util.Timer;
056 import java.util.TimerTask;
057 
058 import javax.swing.AbstractAction;
059 import javax.swing.AbstractListModel;
060 import javax.swing.Action;
061 import javax.swing.BorderFactory;
062 import javax.swing.Box;
063 import javax.swing.BoxLayout;
064 import javax.swing.DefaultListCellRenderer;
065 import javax.swing.GroupLayout;
066 import javax.swing.Icon;
067 import javax.swing.InputMap;
068 import javax.swing.JButton;
069 import javax.swing.JFileChooser;
070 import javax.swing.JLabel;
071 import javax.swing.JList;
072 import javax.swing.JOptionPane;
073 import javax.swing.JPanel;
074 import javax.swing.JScrollPane;
075 import javax.swing.JSplitPane;
076 import javax.swing.JTable;
077 import javax.swing.JTextField;
078 import javax.swing.JToolBar;
079 import javax.swing.KeyStroke;
080 import javax.swing.ListSelectionModel;
081 import javax.swing.SwingUtilities;
082 import javax.swing.ToolTipManager;
083 import javax.swing.border.TitledBorder;
084 import javax.swing.event.DocumentEvent;
085 import javax.swing.event.DocumentListener;
086 import javax.swing.event.ListSelectionEvent;
087 import javax.swing.event.ListSelectionListener;
088 import javax.swing.table.AbstractTableModel;
089 
090 @SuppressWarnings("serial")
091 public class AvailablePlugins extends JPanel {
092 
093   private XJTable mainTable;
094 
095   /**
096    * Contains the URLs from Gate.getKnownPlugins() that satisfy the filter
097    * filterTextField for the plugin URL and the plugin resources names
098    */
099   private List<URL> visibleRows = new ArrayList<URL>();
100 
101   private JSplitPane mainSplit;
102 
103   private MainTableModel mainTableModel;
104 
105   private ResourcesListModel resourcesListModel;
106 
107   private JList<ResourceInfo> resourcesList;
108 
109   private JTextField filterTextField;
110 
111   /**
112    * Map from URL to Boolean. Stores temporary values for the loadNow options.
113    */
114   private Map<URL, Boolean> loadNowByURL = new HashMap<URL, Boolean>();
115 
116   /**
117    * Map from URL to Boolean. Stores temporary values for the loadAlways
118    * options.
119    */
120   private Map<URL, Boolean> loadAlwaysByURL = new HashMap<URL, Boolean>();
121 
122   private static final int ICON_COLUMN = 0;
123 
124   private static final int NAME_COLUMN = 3;
125 
126   private static final int LOAD_NOW_COLUMN = 1;
127 
128   private static final int LOAD_ALWAYS_COLUMN = 2;
129 
130   public AvailablePlugins() {
131     JToolBar tbPluginDirs = new JToolBar(JToolBar.HORIZONTAL);
132     tbPluginDirs.setFloatable(false);
133     tbPluginDirs.setLayout(new BoxLayout(tbPluginDirs, BoxLayout.X_AXIS));
134     tbPluginDirs.add(new JButton(new AddCreoleRepositoryAction()));
135     tbPluginDirs.add(new JButton(new DeleteCreoleRepositoryAction()));
136     tbPluginDirs.add(Box.createHorizontalStrut(5));
137     JLabel titleLabel = new JLabel("CREOLE Plugin Directories");
138     titleLabel.setBorder(BorderFactory.createEmptyBorder(00040));
139     tbPluginDirs.add(titleLabel);
140     tbPluginDirs.add(Box.createHorizontalGlue());
141     tbPluginDirs.add(new JLabel("Filter:"));
142     filterTextField = new JTextField();
143     filterTextField.setToolTipText("Type some text to filter the table rows.");
144     tbPluginDirs.add(filterTextField);
145     JButton clearFilterButton =
146             new JButton(new AbstractAction(null, new ResetIcon(2424)) {
147               {
148                 this.putValue(MNEMONIC_KEY, KeyEvent.VK_BACK_SPACE);
149                 this.putValue(SHORT_DESCRIPTION, "Clear text field");
150               }
151 
152               @Override
153               public void actionPerformed(ActionEvent e) {
154                 filterTextField.setText("");
155                 filterTextField.requestFocusInWindow();
156               }
157             });
158 
159     tbPluginDirs.add(clearFilterButton);
160 
161     mainTableModel = new MainTableModel();
162     mainTable = new XJTable(mainTableModel);
163     mainTable.setTabSkipUneditableCell(true);
164     mainTable.setSortedColumn(NAME_COLUMN);
165 
166     Collator collator = Collator.getInstance(Locale.ENGLISH);
167     collator.setStrength(Collator.TERTIARY);
168     mainTable.setComparator(NAME_COLUMN, collator);
169     mainTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
170     mainTable.getColumnModel().getColumn(ICON_COLUMN)
171             .setCellRenderer(new IconTableCellRenderer());
172     CheckBoxTableCellRenderer cbCellRenderer = new CheckBoxTableCellRenderer();
173     mainTable.getColumnModel().getColumn(LOAD_ALWAYS_COLUMN)
174             .setCellRenderer(cbCellRenderer);
175     mainTable.getColumnModel().getColumn(LOAD_NOW_COLUMN)
176             .setCellRenderer(cbCellRenderer);
177 
178     resourcesListModel = new ResourcesListModel();
179     resourcesList = new JList<ResourceInfo>(resourcesListModel);
180     resourcesList.setCellRenderer(new ResourcesListCellRenderer());
181 
182     // this is needed because otherwise the list gets really narrow most of the
183     // time. Strangely if we don't use a custom cell renderer it works fine so
184     // that must be where the actual bug is
185     ResourceInfo prototype =
186             new ResourceInfo("A rather silly long resource name",
187                     "java.lang.String""this is a comment");
188     resourcesList.setPrototypeCellValue(prototype);
189     resourcesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
190 
191     // enable tooltips
192     ToolTipManager.sharedInstance().registerComponent(resourcesList);
193 
194     mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
195     mainSplit.setResizeWeight(0.80);
196     mainSplit.setContinuousLayout(true);
197     JScrollPane scroller = new JScrollPane(mainTable);
198     scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
199     mainSplit.setLeftComponent(scroller);
200 
201     scroller = new JScrollPane(resourcesList);
202     scroller.setBorder(BorderFactory.createTitledBorder(scroller.getBorder(),
203             "Resources in Plugin", TitledBorder.LEFT, TitledBorder.ABOVE_TOP));
204     mainSplit.setRightComponent(scroller);
205 
206     setLayout(new BorderLayout());
207 
208     add(tbPluginDirs, BorderLayout.NORTH);
209     add(mainSplit, BorderLayout.CENTER);
210 
211     mainTable.getSelectionModel().addListSelectionListener(
212             new ListSelectionListener() {
213               @Override
214               public void valueChanged(ListSelectionEvent e) {
215                 if(!e.getValueIsAdjusting()) {
216                   resourcesListModel.dataChanged();
217                 }
218               }
219             });
220 
221     // when typing a character in the table, use it for filtering
222     mainTable.addKeyListener(new KeyAdapter() {
223 
224       private Action a = new DeleteCreoleRepositoryAction();
225 
226       @Override
227       public void keyTyped(KeyEvent e) {
228         // if you are doing something other than Shift+ then you probably don't
229         // want to use it for filtering
230         if(e.getModifiers() 1return;
231 
232         // if the user presses delete then act as if they pressed the delete
233         // button on the toolbar
234         if(e.getKeyChar() == KeyEvent.VK_DELETE) {
235           a.actionPerformed(null);
236           return;
237         }
238 
239         // these are used for table navigation and not filtering
240         if(e.getKeyChar() == KeyEvent.VK_TAB
241             || e.getKeyChar() == KeyEvent.VK_SPACEreturn;
242 
243         // we want to filter so move the character to the filter text field
244         filterTextField.requestFocusInWindow();
245         filterTextField.setText(String.valueOf(e.getKeyChar()));
246 
247       }
248     });
249 
250     // show only the rows containing the text from filterTextField
251     filterTextField.getDocument().addDocumentListener(new DocumentListener() {
252       private Timer timer = new Timer("Plugin manager table rows filter"true);
253 
254       private TimerTask timerTask;
255 
256       @Override
257       public void changedUpdate(DocumentEvent e) {
258         /* do nothing */
259       }
260 
261       @Override
262       public void insertUpdate(DocumentEvent e) {
263         update();
264       }
265 
266       @Override
267       public void removeUpdate(DocumentEvent e) {
268         update();
269       }
270 
271       private void update() {
272         if(timerTask != null) {
273           timerTask.cancel();
274         }
275         Date timeToRun = new Date(System.currentTimeMillis() 300);
276         timerTask = new TimerTask() {
277           @Override
278           public void run() {
279             filterRows(filterTextField.getText());
280           }
281         };
282         // add a delay
283         timer.schedule(timerTask, timeToRun);
284       }
285     });
286 
287     // Up/Down key events in filterTextField are transferred to the table
288     filterTextField.addKeyListener(new KeyAdapter() {
289       @Override
290       public void keyPressed(KeyEvent e) {
291         if(e.getKeyCode() == KeyEvent.VK_UP
292                 || e.getKeyCode() == KeyEvent.VK_DOWN
293                 || e.getKeyCode() == KeyEvent.VK_PAGE_UP
294                 || e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) {
295           mainTable.dispatchEvent(e);
296         }
297       }
298     });
299 
300     // disable Enter key in the table so this key will confirm the dialog
301     InputMap inputMap =
302             mainTable.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
303     KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
304     inputMap.put(enter, "none");
305 
306     reInit();
307   }
308 
309   protected void reInit() {
310     loadNowByURL.clear();
311     loadAlwaysByURL.clear();
312     visibleRows.clear();
313     visibleRows.addAll(Gate.getKnownPlugins());
314     if(mainTable.getRowCount() 0mainTable.setRowSelectionInterval(00);
315     filterRows("");
316   }
317 
318   private void filterRows(String rowFilter) {
319     final String filter = rowFilter.trim().toLowerCase();
320     final String previousURL =
321             mainTable.getSelectedRow() == -"" (String)mainTable
322                     .getValueAt(mainTable.getSelectedRow(),
323                             mainTable.convertColumnIndexToView(NAME_COLUMN));
324     if(filter.length() 2) {
325       // one character or less, don't filter rows
326       visibleRows.clear();
327       visibleRows.addAll(Gate.getKnownPlugins());
328     else {
329       // filter rows case insensitively on each plugin URL and its resources
330       visibleRows.clear();
331       for(int i = 0; i < Gate.getKnownPlugins().size(); i++) {
332         Gate.DirectoryInfo dInfo =
333                 Gate.getDirectoryInfo(Gate.getKnownPlugins().get(i));
334         String url = dInfo.getUrl().toString();
335         String resources = "";
336         for(int j = 0; j < dInfo.getResourceInfoList().size(); j++) {
337           resources +=
338                   dInfo.getResourceInfoList().get(j).getResourceName() " ";
339         }
340         if(url.toLowerCase().contains(filter)
341                 || resources.toLowerCase().contains(filter)) {
342           visibleRows.add(Gate.getKnownPlugins().get(i));
343         }
344       }
345     }
346 
347     mainTableModel.fireTableDataChanged();
348 
349     if(mainTable.getRowCount() 0) {
350       SwingUtilities.invokeLater(new Runnable() {
351         @Override
352         public void run() {
353           mainTable.setRowSelectionInterval(00);
354           if(filter.length() && previousURL != null
355                   && !previousURL.equals("")) {
356             // reselect the last selected row based on its name and url values
357             for(int row = 0; row < mainTable.getRowCount(); row++) {
358               String url =
359                       (String)mainTable.getValueAt(row,
360                               mainTable.convertColumnIndexToView(NAME_COLUMN));
361               if(url.contains(previousURL)) {
362                 mainTable.setRowSelectionInterval(row, row);
363                 mainTable.scrollRectToVisible(mainTable.getCellRect(row, 0,
364                         true));
365                 break;
366               }
367             }
368           }
369         }
370       });
371     }
372   }
373 
374   private Boolean getLoadNow(URL url) {
375     Boolean res = loadNowByURL.get(url);
376     if(res == null) {
377       res = Gate.getCreoleRegister().getDirectories().contains(url);
378       loadNowByURL.put(url, res);
379     }
380     return res;
381   }
382 
383   private Boolean getLoadAlways(URL url) {
384     Boolean res = loadAlwaysByURL.get(url);
385     if(res == null) {
386       res = Gate.getAutoloadPlugins().contains(url);
387       loadAlwaysByURL.put(url, res);
388     }
389     return res;
390   }
391 
392   private class MainTableModel extends AbstractTableModel {
393 
394     private Icon coreIcon, userIcon, remoteIcon, otherIcon, invalidIcon;
395 
396     public MainTableModel() {
397       otherIcon = new OpenFileIcon(3232);
398       coreIcon = new GATEIcon(3232);
399       userIcon = new UserPluginIcon(3232);
400       remoteIcon = new RemotePluginIcon(3232);
401       invalidIcon = new InvalidIcon(3232);
402     }
403 
404     @Override
405     public int getRowCount() {
406       return visibleRows.size();
407     }
408 
409     @Override
410     public int getColumnCount() {
411       return 4;
412     }
413 
414     @Override
415     public String getColumnName(int column) {
416       switch(column){
417         case NAME_COLUMN:
418           return "<html><body style='padding: 2px; text-align: center;'>Plugin Name</body></html>";
419         case ICON_COLUMN:
420           return null;
421         case LOAD_NOW_COLUMN:
422           return "<html><body style='padding: 2px; text-align: center;'>Load<br>Now</body></html>";
423         case LOAD_ALWAYS_COLUMN:
424           return "<html><body style='padding: 2px; text-align: center;'>Load<br>Always</body></html>";
425         default:
426           return "?";
427       }
428     }
429 
430     @Override
431     public Class<?> getColumnClass(int columnIndex) {
432       switch(columnIndex){
433         case NAME_COLUMN:
434           return String.class;
435         case ICON_COLUMN:
436           return Icon.class;
437         case LOAD_NOW_COLUMN:
438         case LOAD_ALWAYS_COLUMN:
439           return Boolean.class;
440         default:
441           return Object.class;
442       }
443     }
444 
445     @Override
446     public Object getValueAt(int row, int column) {
447       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(visibleRows.get(row));
448       if(dInfo == null) { return null}
449       switch(column){
450         case NAME_COLUMN:
451           return dInfo.toHTMLString();
452         case ICON_COLUMN:
453           if(!dInfo.isValid()) return invalidIcon;
454           if(dInfo.isRemotePlugin()) return remoteIcon;
455           if(dInfo.isCorePlugin()) return coreIcon;
456           if(dInfo.isUserPlugin()) return userIcon;
457           return otherIcon;
458         case LOAD_NOW_COLUMN:
459           return getLoadNow(dInfo.getUrl());
460         case LOAD_ALWAYS_COLUMN:
461           return getLoadAlways(dInfo.getUrl());
462         default:
463           return null;
464       }
465     }
466 
467     @Override
468     public boolean isCellEditable(int row, int column) {
469       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(visibleRows.get(row));
470       return dInfo.isValid()
471               && (column == LOAD_NOW_COLUMN || column == LOAD_ALWAYS_COLUMN);
472     }
473 
474     @Override
475     public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
476       Boolean valueBoolean = (Boolean)aValue;
477       Gate.DirectoryInfo dInfo =
478               Gate.getDirectoryInfo(visibleRows.get(rowIndex));
479       if(dInfo == null) { return}
480 
481       switch(columnIndex){
482         case LOAD_NOW_COLUMN:
483           loadNowByURL.put(dInfo.getUrl(), valueBoolean);
484           // for some reason the focus is sometime lost after editing
485           // however it is needed for Enter key to execute OkAction
486           mainTable.requestFocusInWindow();
487           break;
488         case LOAD_ALWAYS_COLUMN:
489           loadAlwaysByURL.put(dInfo.getUrl(), valueBoolean);
490           mainTable.requestFocusInWindow();
491           break;
492       }
493     }
494   }
495 
496   private class ResourcesListModel extends AbstractListModel<ResourceInfo> {
497 
498     @Override
499     public ResourceInfo getElementAt(int index) {
500       int row = mainTable.getSelectedRow();
501       if(row == -1return null;
502       row = mainTable.rowViewToModel(row);
503       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(visibleRows.get(row));
504       return dInfo.getResourceInfoList().get(index);
505     }
506 
507     @Override
508     public int getSize() {
509       int row = mainTable.getSelectedRow();
510       if(row == -1return 0;
511       row = mainTable.rowViewToModel(row);
512       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(visibleRows.get(row));
513       if(dInfo == null) { return 0}
514       return dInfo.getResourceInfoList().size();
515     }
516 
517     public void dataChanged() {
518       fireContentsChanged(this, 0, getSize() 1);
519     }
520   }
521 
522   private class ResourcesListCellRenderer extends DefaultListCellRenderer {
523 
524     @Override
525     public Component getListCellRendererComponent(JList<?> list, Object value,
526             int index, boolean isSelected, boolean cellHasFocus) {
527       Gate.ResourceInfo rInfo = (Gate.ResourceInfo)value;
528       // prepare the renderer
529       String filter = filterTextField.getText().trim().toLowerCase();
530       if(filter.length() 1
531               && rInfo.getResourceName().toLowerCase().contains(filter)) {
532         isSelected = true// select resource if matching table row filter
533       }
534       super.getListCellRendererComponent(list, rInfo.getResourceName(), index,
535               isSelected, cellHasFocus);
536       // add tooltip text
537       setToolTipText(rInfo.getResourceComment());
538       return this;
539     }
540   }
541 
542   protected boolean unsavedChanges() {
543 
544     Set<URL> creoleDirectories = Gate.getCreoleRegister().getDirectories();
545 
546     Iterator<URL> pluginIter = loadNowByURL.keySet().iterator();
547     while(pluginIter.hasNext()) {
548       URL aPluginURL = pluginIter.next();
549       boolean load = loadNowByURL.get(aPluginURL);
550       boolean loaded = creoleDirectories.contains(aPluginURL);
551       if(load && !loaded) { return true}
552       if(!load && loaded) { return true}
553     }
554 
555     pluginIter = loadAlwaysByURL.keySet().iterator();
556     while(pluginIter.hasNext()) {
557       URL aPluginURL = pluginIter.next();
558       boolean load = loadAlwaysByURL.get(aPluginURL);
559       boolean loaded = Gate.getAutoloadPlugins().contains(aPluginURL);
560       if(load && !loaded) { return true}
561       if(!load && loaded) { return true}
562     }
563 
564     return false;
565   }
566 
567   protected Set<URL> updateAvailablePlugins() {
568 
569     Set<URL> creoleDirectories = Gate.getCreoleRegister().getDirectories();
570 
571     // update the data structures to reflect the user's choices
572     Iterator<URL> pluginIter = loadNowByURL.keySet().iterator();
573     
574     Set<URL> toLoad = new HashSet<URL>();
575     while(pluginIter.hasNext()) {
576       URL aPluginURL = pluginIter.next();
577       boolean load = loadNowByURL.get(aPluginURL);
578       boolean loaded = creoleDirectories.contains(aPluginURL);
579       if(load && !loaded) {
580         // remember that we need to load this plugin
581         toLoad.add(aPluginURL);
582       }
583       if(!load && loaded) {
584         // remove the directory
585         Gate.getCreoleRegister().removeDirectory(aPluginURL);
586       }
587     }
588 
589     pluginIter = loadAlwaysByURL.keySet().iterator();
590     while(pluginIter.hasNext()) {
591       URL aPluginURL = pluginIter.next();
592       boolean load = loadAlwaysByURL.get(aPluginURL);
593       boolean loaded = Gate.getAutoloadPlugins().contains(aPluginURL);
594       if(load && !loaded) {
595         // set autoload to true
596         Gate.addAutoloadPlugin(aPluginURL);
597       }
598       if(!load && loaded) {
599         // set autoload to false
600         Gate.removeAutoloadPlugin(aPluginURL);
601       }
602     }
603     
604     while(!toLoad.isEmpty()) {
605       //lets finally try loading all the plugings
606       int numToLoad = toLoad.size();
607       List<Throwable> errors = new ArrayList<Throwable>();
608       
609       pluginIter = toLoad.iterator();
610       while(pluginIter.hasNext()) {
611         URL aPluginURL = pluginIter.next();
612         
613         // load the directory
614         try {
615           Gate.getCreoleRegister().registerDirectories(aPluginURL);
616           pluginIter.remove();
617         catch(Throwable ge) {
618           //TODO suppress the errors unless we are going to break out of the loop
619           //ge.printStackTrace();
620           errors.add(ge);
621         }
622       }
623       
624       if (numToLoad == toLoad.size()) {
625         //we tried loading all the plugins and yet
626         //we didn't actually achieve anything
627         for (Throwable t : errors) {
628           t.printStackTrace();
629         }        
630         
631         break;
632       }
633     }
634     
635     loadNowByURL.clear();
636     loadAlwaysByURL.clear();
637     
638     return toLoad;
639   }
640 
641   private class DeleteCreoleRepositoryAction extends AbstractAction {
642     public DeleteCreoleRepositoryAction() {
643       super(null, new RemoveIcon(2424));
644       putValue(SHORT_DESCRIPTION, "Unregister selected CREOLE directory");
645     }
646 
647     @Override
648     public void actionPerformed(ActionEvent arg0) {
649       int[] rows = mainTable.getSelectedRows();
650 
651       for(int row : rows) {
652         int rowModel = mainTable.rowViewToModel(row);
653 
654         URL toDelete = visibleRows.get(rowModel);
655 
656         Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(toDelete);
657 
658         Gate.getCreoleRegister().removeDirectory(toDelete);
659 
660         if(!dInfo.isCorePlugin() && !dInfo.isUserPlugin()) {
661           Gate.removeKnownPlugin(toDelete);
662           loadAlwaysByURL.remove(toDelete);
663           loadNowByURL.remove(toDelete);
664         }
665       }
666 
667       // redisplay the table with the current filter
668       filterRows(filterTextField.getText());
669     }
670   }
671 
672   private class AddCreoleRepositoryAction extends AbstractAction {
673     public AddCreoleRepositoryAction() {
674       super(null, new AddIcon(2424));
675       putValue(SHORT_DESCRIPTION, "Register a new CREOLE directory");
676     }
677 
678     @Override
679     public void actionPerformed(ActionEvent e) {
680       JTextField urlTextField = new JTextField(20);
681 
682       class URLfromFileAction extends AbstractAction {
683         URLfromFileAction(JTextField textField) {
684           super(null, new OpenFileIcon(2424));
685           putValue(SHORT_DESCRIPTION, "Click to select a directory");
686           this.textField = textField;
687         }
688 
689         @Override
690         public void actionPerformed(ActionEvent e) {
691           XJFileChooser fileChooser = MainFrame.getFileChooser();
692           fileChooser.setMultiSelectionEnabled(false);
693           fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
694           fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
695           fileChooser.setResource("gate.CreoleRegister");
696           int result = fileChooser.showOpenDialog(AvailablePlugins.this);
697           if(result == JFileChooser.APPROVE_OPTION) {
698             try {
699               textField.setText(fileChooser.getSelectedFile().toURI().toURL()
700                       .toExternalForm());
701             catch(MalformedURLException mue) {
702               throw new GateRuntimeException(mue.toString());
703             }
704           }
705         }
706 
707         JTextField textField;
708       }
709 
710       JButton fileBtn = new JButton(new URLfromFileAction(urlTextField));
711 
712       JPanel message = new JPanel();
713       GroupLayout msgLayout = new GroupLayout(message);
714       message.setLayout(msgLayout);
715 
716       msgLayout.setAutoCreateContainerGaps(true);
717       msgLayout.setAutoCreateGaps(true);
718 
719       JLabel lblURL = new JLabel("Type a URL");
720       JLabel lblDir = new JLabel("Select a Directory");
721       JLabel lblOR = new JLabel("or");
722 
723       msgLayout
724               .setHorizontalGroup(msgLayout
725                       .createSequentialGroup()
726                       .addGroup(
727                               msgLayout.createParallelGroup()
728                                       .addComponent(lblURL)
729                                       .addComponent(urlTextField))
730                       .addComponent(lblOR)
731                       .addGroup(
732                               msgLayout
733                                       .createParallelGroup(
734                                               GroupLayout.Alignment.CENTER)
735                                       .addComponent(lblDir)
736                                       .addComponent(fileBtn)));
737 
738       msgLayout
739               .setVerticalGroup(msgLayout
740                       .createSequentialGroup()
741                       .addGroup(
742                               msgLayout.createParallelGroup()
743                                       .addComponent(lblURL)
744                                       .addComponent(lblDir))
745 
746                       .addGroup(
747                               msgLayout
748                                       .createParallelGroup(
749                                               GroupLayout.Alignment.CENTER)
750                                       .addComponent(urlTextField)
751                                       .addComponent(lblOR)
752                                       .addComponent(fileBtn)));
753 
754       if(JOptionPane.showConfirmDialog(AvailablePlugins.this, message,
755               "Register a new CREOLE directory", JOptionPane.OK_CANCEL_OPTION,
756               JOptionPane.QUESTION_MESSAGE, new AvailableIcon(4848)) != JOptionPane.OK_OPTION)
757         return;
758 
759       try {
760         final URL creoleURL = new URL(urlTextField.getText());
761         Gate.addKnownPlugin(creoleURL);
762         mainTable.clearSelection();
763         // redisplay the table without filtering
764         filterRows("");
765         // clear the filter text field
766         filterTextField.setText("");
767         // select the new plugin row
768         SwingUtilities.invokeLater(new Runnable() {
769 
770           @Override
771           public void run() {
772             for(int row = 0; row < mainTable.getRowCount(); row++) {
773               String url =
774                       (String)mainTable.getValueAt(row,
775                               mainTable.convertColumnIndexToView(NAME_COLUMN));
776               if(url.contains(creoleURL.toString())) {
777                 mainTable.setRowSelectionInterval(row, row);
778                 mainTable.scrollRectToVisible(mainTable.getCellRect(row, 0,
779                         true));
780                 break;
781               }
782             }
783           }
784         });
785         mainTable.requestFocusInWindow();
786       catch(Exception ex) {
787 
788         JOptionPane
789                 .showMessageDialog(
790                         AvailablePlugins.this,
791                         "<html><body style='width: 350px;'><b>Unable to register CREOLE directory!</b><br><br>"
792                                 "The URL you specified is not valid. Please check the URL and try again.</body></html>",
793                         "CREOLE Plugin Manager", JOptionPane.ERROR_MESSAGE);
794       }
795     }
796   }
797 }