1   /*
2    *  Copyright (c) 1998-2004, The University of Sheffield.
3    *
4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
5    *  software, licenced under the GNU Library General Public License,
6    *  Version 2, June 1991 (in the distribution as file licence.html,
7    *  and also available at http://gate.ac.uk/gate/licence.html).
8    *
9    *  PluginManagerUI.java
10   *
11   *  Valentin Tablan, 21-Jul-2004
12   *
13   *  $Id: PluginManagerUI.java,v 1.10 2004/12/06 17:02:45 valyt Exp $
14   */
15  
16  package gate.gui;
17  
18  import java.awt.*;
19  import java.awt.GridBagConstraints;
20  import java.awt.GridBagLayout;
21  import java.awt.event.*;
22  import java.awt.event.ActionEvent;
23  import java.awt.event.ActionListener;
24  import java.io.File;
25  import java.io.IOException;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.util.*;
29  import java.util.ArrayList;
30  import java.util.List;
31  import javax.swing.*;
32  import javax.swing.border.TitledBorder;
33  import javax.swing.event.ListSelectionEvent;
34  import javax.swing.event.ListSelectionListener;
35  import javax.swing.table.*;
36  import javax.swing.table.AbstractTableModel;
37  import javax.swing.table.TableCellRenderer;
38  import org.jdom.*;
39  import org.jdom.Element;
40  import org.jdom.JDOMException;
41  import org.jdom.input.SAXBuilder;
42  import gate.Gate;
43  import gate.GateConstants;
44  import gate.event.CreoleListener;
45  import gate.swing.XJTable;
46  import gate.util.*;
47  import gate.util.Err;
48  import gate.util.GateRuntimeException;
49  
50  /**
51   * This is the user interface used for plugin management 
52   */
53  public class PluginManagerUI extends JDialog implements GateConstants{
54    
55    public PluginManagerUI(Frame owner){
56      super(owner);
57      initLocalData();
58      initGUI();
59      initListeners();
60    }
61    
62    
63    protected void initLocalData(){
64      loadNowByURL = new HashMap();
65      loadAlwaysByURL = new HashMap();
66    }
67    
68    protected void initGUI(){
69      setTitle("Plugin Management Console");
70      mainTableModel = new MainTableModel();
71      mainTable = new XJTable();
72  //    mainTable.setSortable(false);
73      mainTable.setModel(mainTableModel);
74  //    mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
75      mainTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
76      DeleteColumnCellRendererEditor rendererEditor = new DeleteColumnCellRendererEditor();
77      mainTable.getColumnModel().getColumn(DELETE_COLUMN).
78        setCellEditor(rendererEditor);
79      mainTable.getColumnModel().getColumn(DELETE_COLUMN).
80        setCellRenderer(rendererEditor);
81      
82      resourcesListModel = new ResourcesListModel();
83      resourcesList = new JList(resourcesListModel);
84      resourcesList.setCellRenderer(new ResourcesListCellRenderer());
85      resourcesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
86      //enable tooltips
87      ToolTipManager.sharedInstance().registerComponent(resourcesList);
88      
89      mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
90      JScrollPane scroller = new JScrollPane(mainTable);
91      scroller.setBorder(BorderFactory.createTitledBorder(
92              scroller.getBorder(), 
93              "Known CREOLE directories", 
94              TitledBorder.LEFT, TitledBorder.ABOVE_TOP));
95      mainSplit.setLeftComponent(scroller);
96      
97      scroller = new JScrollPane(resourcesList);
98      scroller.setBorder(BorderFactory.createTitledBorder(
99              scroller.getBorder(), 
100             "CREOLE resources in directory",
101             TitledBorder.LEFT, TitledBorder.ABOVE_TOP));
102     mainSplit.setRightComponent(scroller);
103     
104     getContentPane().setLayout(new GridBagLayout());
105     GridBagConstraints constraints = new GridBagConstraints();
106     constraints.insets = new Insets(2, 2, 2, 2);
107     constraints.fill = GridBagConstraints.BOTH;
108     constraints.anchor = GridBagConstraints.WEST;
109     constraints.gridy = 0;
110     constraints.weightx = 1;
111     constraints.weighty = 1;
112     getContentPane().add(mainSplit, constraints);
113     
114     constraints.gridy = 1;
115     constraints.weighty = 0;
116     Box hBox = Box.createHorizontalBox();
117     hBox.add(new JLabel("You can also "));
118     hBox.add(new JButton(new AddCreoleRepositoryAction()));
119     hBox.add(Box.createHorizontalGlue());
120     getContentPane().add(hBox, constraints);
121     
122     constraints.gridy = 2;
123     constraints.anchor = GridBagConstraints.CENTER;
124     constraints.fill = GridBagConstraints.NONE;
125     hBox = Box.createHorizontalBox();
126     hBox.add(new JButton(new OkAction()));
127     hBox.add(Box.createHorizontalStrut(20));
128     hBox.add(new JButton(new CancelAction()));
129     getContentPane().add(hBox, constraints);
130   }
131   
132   protected void initListeners(){
133     mainTable.getSelectionModel().addListSelectionListener(
134       new ListSelectionListener(){
135      public void valueChanged(ListSelectionEvent e){
136        resourcesListModel.dataChanged();
137      }
138     });
139     mainSplit.addComponentListener(new ComponentAdapter(){
140       public void componentResized(ComponentEvent e){
141         mainSplit.setDividerLocation(0.75);
142       }
143     });
144   }
145   
146   protected Boolean getLoadNow(URL url){
147     Boolean res = (Boolean)loadNowByURL.get(url);
148     if(res == null){
149       res = new Boolean(Gate.getCreoleRegister().getDirectories().contains(url));
150       loadNowByURL.put(url, res);
151     }
152     return res;
153   }
154   
155   protected Boolean getLoadAlways(URL url){
156     Boolean res = (Boolean)loadAlwaysByURL.get(url);
157     if(res == null){
158       res = new Boolean(Gate.getAutoloadPlugins().contains(url));
159       loadAlwaysByURL.put(url, res);
160     }
161     return res;
162   }
163   
164   protected class MainTableModel extends AbstractTableModel{
165     public MainTableModel(){
166       localIcon = MainFrame.getIcon("loadFile.gif");
167       remoteIcon = MainFrame.getIcon("internet.gif");
168       invalidIcon = MainFrame.getIcon("param.gif");
169     }
170     public int getRowCount(){
171       return Gate.getKnownPlugins().size();
172     }
173     
174     public int getColumnCount(){
175       return 6;
176     }
177     
178     public String getColumnName(int column){
179       switch (column){
180         case NAME_COLUMN: return "Name";
181         case ICON_COLUMN: return "";
182         case URL_COLUMN: return "URL";
183         case LOAD_NOW_COLUMN: return "Load now";
184         case LOAD_ALWAYS_COLUMN: return "Load always";
185         case DELETE_COLUMN: return "Delete";
186         default: return "?";
187       }
188     }
189     
190     public Class getColumnClass(int columnIndex){
191       switch (columnIndex){
192         case NAME_COLUMN: return String.class;
193         case ICON_COLUMN: return Icon.class;
194         case URL_COLUMN: return String.class;
195         case LOAD_NOW_COLUMN: return Boolean.class;
196         case LOAD_ALWAYS_COLUMN: return Boolean.class;
197         case DELETE_COLUMN: return Object.class;
198         default: return Object.class;
199       }
200     }
201     
202     public Object getValueAt(int row, int column){
203       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
204               (URL)Gate.getKnownPlugins().get(row));
205       switch (column){
206         case NAME_COLUMN: return new File(dInfo.getUrl().getFile()).getName();
207         case ICON_COLUMN: return
208           dInfo.isValid() ? (
209             dInfo.getUrl().getProtocol().equalsIgnoreCase("file") ? 
210             localIcon : remoteIcon) :
211           invalidIcon;
212         case URL_COLUMN: return dInfo.getUrl().toString();
213         case LOAD_NOW_COLUMN: return  getLoadNow(dInfo.getUrl());
214         case LOAD_ALWAYS_COLUMN: return getLoadAlways(dInfo.getUrl());
215         case DELETE_COLUMN: return null;
216         default: return null;
217       }
218     }
219     
220     public boolean isCellEditable(int rowIndex, int columnIndex){
221       return columnIndex == LOAD_NOW_COLUMN || 
222         columnIndex == LOAD_ALWAYS_COLUMN ||
223         columnIndex == DELETE_COLUMN;
224     }
225     
226     public void setValueAt(Object aValue, int rowIndex, int columnIndex){
227       Boolean valueBoolean = (Boolean)aValue;
228       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
229               (URL)Gate.getKnownPlugins().get(rowIndex));
230       switch(columnIndex){
231         case LOAD_NOW_COLUMN: 
232           loadNowByURL.put(dInfo.getUrl(), valueBoolean);
233           break;
234         case LOAD_ALWAYS_COLUMN:
235           loadAlwaysByURL.put(dInfo.getUrl(), valueBoolean);
236           break;
237       }
238     }
239     
240     protected Icon localIcon;
241     protected Icon remoteIcon;
242     protected Icon invalidIcon;
243   }
244   
245   protected class ResourcesListModel extends AbstractListModel{
246     public Object getElementAt(int index){
247       int row = mainTable.getSelectedRow();
248       if(row == -1) return null;
249       row = mainTable.rowViewToModel(row);
250       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
251               (URL)Gate.getKnownPlugins().get(row));
252       return (Gate.ResourceInfo)dInfo.getResourceInfoList().get(index);
253     }
254     
255     public int getSize(){
256       
257       int row = mainTable.getSelectedRow();
258       if(row == -1) return 0;
259       row = mainTable.rowViewToModel(row);
260       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
261               (URL)Gate.getKnownPlugins().get(row));
262       return dInfo.getResourceInfoList().size();
263     }
264     
265     public void dataChanged(){
266 //      fireIntervalRemoved(this, 0, getSize() - 1);
267 //      fireIntervalAdded(this, 0, getSize() - 1);
268       fireContentsChanged(this, 0, getSize() - 1);
269     }
270   }
271   
272   /**
273    * This class acts both as cell renderer  and editor for all the cells in the 
274    * delete column.
275    */
276   protected class DeleteColumnCellRendererEditor extends AbstractCellEditor 
277     implements TableCellRenderer, TableCellEditor{
278     
279     public DeleteColumnCellRendererEditor(){
280       label = new JLabel();
281       rendererDeleteButton = new JButton(MainFrame.getIcon("delete.gif"));
282       rendererDeleteButton.setMaximumSize(rendererDeleteButton.getPreferredSize());
283       editorDeleteButton = new JButton(MainFrame.getIcon("delete.gif"));
284       editorDeleteButton.addActionListener(new ActionListener(){
285         public void actionPerformed(ActionEvent evt){
286           int row = mainTable.getEditingRow();
287           row = mainTable.rowViewToModel(row);
288           URL toDelete = (URL)Gate.getKnownPlugins().get(row);
289           Gate.removeKnownPlugin(toDelete);
290           loadAlwaysByURL.remove(toDelete);
291           loadNowByURL.remove(toDelete);
292           mainTableModel.fireTableDataChanged();
293           resourcesListModel.dataChanged();
294         }
295       });
296     }
297     
298     public Component getTableCellRendererComponent(JTable table,
299             Object value,
300             boolean isSelected,
301             boolean hasFocus,
302             int row,
303             int column){
304 //      editorDeleteButton.setSelected(false);
305       switch(column){
306         case DELETE_COLUMN:
307           return rendererDeleteButton;
308         default: return null;
309       }
310     }
311     
312     public Component getTableCellEditorComponent(JTable table,
313             Object value,
314             boolean isSelected,
315             int row,
316             int column){
317       switch(column){
318         case DELETE_COLUMN:
319           return editorDeleteButton;
320         default: return null;
321       }
322     }
323     
324     public Object getCellEditorValue(){
325       return null;
326     }
327     
328     JButton editorDeleteButton;
329     JButton rendererDeleteButton;
330     JLabel label;
331   }
332   
333   protected class ResourcesListCellRenderer extends DefaultListCellRenderer{
334     public Component getListCellRendererComponent(JList list,
335             Object value,
336             int index,
337             boolean isSelected,
338             boolean cellHasFocus){
339       Gate.ResourceInfo rInfo = (Gate.ResourceInfo)value;
340       //prepare the renderer
341       super.getListCellRendererComponent(list, 
342               rInfo.getResourceName(), index, isSelected, cellHasFocus);
343       //add tooltip text
344       setToolTipText(rInfo.getResourceComment());
345       return this;
346     }
347   }
348   
349   
350   protected class OkAction extends AbstractAction {
351     public OkAction(){
352       super("OK");
353     }
354     public void actionPerformed(ActionEvent evt){
355       setVisible(false);
356       //update the data structures to reflect the user's choices
357       Iterator pluginIter = loadNowByURL.keySet().iterator();
358       while(pluginIter.hasNext()){
359         URL aPluginURL = (URL)pluginIter.next();
360         boolean load = ((Boolean)loadNowByURL.get(aPluginURL)).booleanValue();
361         boolean loaded = Gate.getCreoleRegister().
362             getDirectories().contains(aPluginURL); 
363         if(load && !loaded){
364           //load the directory
365           try{
366             Gate.getCreoleRegister().registerDirectories(aPluginURL);
367           }catch(GateException ge){
368             throw new GateRuntimeException(ge);
369           }
370         }
371         if(!load && loaded){
372           //remove the directory
373           Gate.getCreoleRegister().removeDirectory(aPluginURL);
374         }
375       }
376       
377       
378       pluginIter = loadAlwaysByURL.keySet().iterator();
379       while(pluginIter.hasNext()){
380         URL aPluginURL = (URL)pluginIter.next();
381         boolean load = ((Boolean)loadAlwaysByURL.get(aPluginURL)).booleanValue();
382         boolean loaded = Gate.getAutoloadPlugins().contains(aPluginURL); 
383         if(load && !loaded){
384           //set autoload top true
385           Gate.addAutoloadPlugin(aPluginURL);
386         }
387         if(!load && loaded){
388           //set autoload to false
389           Gate.removeAutoloadPlugin(aPluginURL);
390         }
391       }
392       loadNowByURL.clear();
393       loadAlwaysByURL.clear();
394     }
395   }
396   
397   /**
398    * Overridden so we can populate the UI before showing.
399    */
400   public void setVisible(boolean visible){
401     if(visible){
402       loadNowByURL.clear();
403       loadAlwaysByURL.clear();      
404       mainTableModel.fireTableDataChanged();
405     }
406     super.setVisible(visible);
407   }
408   protected class CancelAction extends AbstractAction {
409     public CancelAction(){
410       super("Cancel");
411     }
412     
413     public void actionPerformed(ActionEvent evt){
414       setVisible(false);
415       loadNowByURL.clear();
416       loadAlwaysByURL.clear();      
417     }
418   }
419 
420   protected class AddCreoleRepositoryAction extends AbstractAction {
421     public AddCreoleRepositoryAction(){
422       super("Add a new CREOLE repository");
423       putValue(SHORT_DESCRIPTION,"Load a new CREOLE repository");
424     }
425 
426     public void actionPerformed(ActionEvent e) {
427       Box messageBox = Box.createHorizontalBox();
428       Box leftBox = Box.createVerticalBox();
429       JTextField urlTextField = new JTextField(20);
430       leftBox.add(new JLabel("Type an URL"));
431       leftBox.add(urlTextField);
432       messageBox.add(leftBox);
433 
434       messageBox.add(Box.createHorizontalStrut(10));
435       messageBox.add(new JLabel("or"));
436       messageBox.add(Box.createHorizontalStrut(10));
437 
438       class URLfromFileAction extends AbstractAction{
439         URLfromFileAction(JTextField textField){
440           super(null, MainFrame.getIcon("loadFile.gif"));
441           putValue(SHORT_DESCRIPTION,"Click to select a directory");
442           this.textField = textField;
443         }
444 
445         public void actionPerformed(ActionEvent e){
446           JFileChooser fileChooser = MainFrame.getFileChooser(); 
447           fileChooser.setMultiSelectionEnabled(false);
448           fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
449           fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
450           int result = fileChooser.showOpenDialog(PluginManagerUI.this);
451           if(result == JFileChooser.APPROVE_OPTION){
452             try{
453               textField.setText(fileChooser.getSelectedFile().
454                                             toURL().toExternalForm());
455             }catch(MalformedURLException mue){
456               throw new GateRuntimeException(mue.toString());
457             }
458           }
459         }
460         JTextField textField;
461       };//class URLfromFileAction extends AbstractAction
462 
463       Box rightBox = Box.createVerticalBox();
464       rightBox.add(new JLabel("Select a directory"));
465       JButton fileBtn = new JButton(new URLfromFileAction(urlTextField));
466       rightBox.add(fileBtn);
467       messageBox.add(rightBox);
468 
469       int res = JOptionPane.showOptionDialog(
470                             PluginManagerUI.this, messageBox,
471                             "Enter an URL to the directory containing the " +
472                             "\"creole.xml\" file", JOptionPane.OK_CANCEL_OPTION,
473                             JOptionPane.QUESTION_MESSAGE, null, null, null);
474       if(res == JOptionPane.OK_OPTION){
475         try{
476           URL creoleURL = new URL(urlTextField.getText());
477           Gate.addKnownPlugin(creoleURL);
478           mainTableModel.fireTableDataChanged();
479         }catch(Exception ex){
480           JOptionPane.showMessageDialog(
481               PluginManagerUI.this,
482               "There was a problem with your selection:\n" +
483               ex.toString() ,
484               "GATE", JOptionPane.ERROR_MESSAGE);
485           ex.printStackTrace(Err.getPrintWriter());
486         }
487       }
488     }
489   }//class LoadCreoleRepositoryAction extends AbstractAction
490 
491   protected XJTable mainTable;
492   protected JSplitPane mainSplit;
493   protected MainTableModel mainTableModel;
494   protected ResourcesListModel resourcesListModel;
495   protected JList resourcesList; 
496   
497   /**
498    * Map from URL to Boolean. Stores temporary values for the loadNow options.
499    */
500   protected Map loadNowByURL;
501   /**
502    * Map from URL to Boolean. Stores temporary values for the loadAlways 
503    * options.
504    */
505   protected Map loadAlwaysByURL;
506  
507   protected static final int ICON_COLUMN = 0;
508   protected static final int NAME_COLUMN = 1;
509   protected static final int URL_COLUMN = 2;
510   protected static final int LOAD_NOW_COLUMN = 3;
511   protected static final int LOAD_ALWAYS_COLUMN = 4;
512   protected static final int DELETE_COLUMN = 5;
513   
514 }
515