1   /*
2    *  Copyright (c) 1998-2005, 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.13 2005/01/12 14:44:51 ian 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      mainSplit.setResizeWeight(.75);
91      JScrollPane scroller = new JScrollPane(mainTable);
92      scroller.setBorder(BorderFactory.createTitledBorder(
93              scroller.getBorder(), 
94              "Known CREOLE directories", 
95              TitledBorder.LEFT, TitledBorder.ABOVE_TOP));
96      mainSplit.setLeftComponent(scroller);
97      
98      scroller = new JScrollPane(resourcesList);
99      scroller.setBorder(BorderFactory.createTitledBorder(
100             scroller.getBorder(), 
101             "CREOLE resources in directory",
102             TitledBorder.LEFT, TitledBorder.ABOVE_TOP));
103     mainSplit.setRightComponent(scroller);
104     
105     getContentPane().setLayout(new GridBagLayout());
106     GridBagConstraints constraints = new GridBagConstraints();
107     constraints.insets = new Insets(2, 2, 2, 2);
108     constraints.fill = GridBagConstraints.BOTH;
109     constraints.anchor = GridBagConstraints.WEST;
110     constraints.gridy = 0;
111     constraints.weightx = 1;
112     constraints.weighty = 1;
113     getContentPane().add(mainSplit, constraints);
114     
115     constraints.gridy = 1;
116     constraints.weighty = 0;
117     Box hBox = Box.createHorizontalBox();
118     hBox.add(new JLabel("You can also "));
119     hBox.add(new JButton(new AddCreoleRepositoryAction()));
120     hBox.add(Box.createHorizontalGlue());
121     getContentPane().add(hBox, constraints);
122     
123     constraints.gridy = 2;
124     constraints.anchor = GridBagConstraints.CENTER;
125     constraints.fill = GridBagConstraints.NONE;
126     hBox = Box.createHorizontalBox();
127     hBox.add(new JButton(new OkAction()));
128     hBox.add(Box.createHorizontalStrut(20));
129     hBox.add(new JButton(new CancelAction()));
130     getContentPane().add(hBox, constraints);
131   }
132   
133   protected void initListeners(){
134     mainTable.getSelectionModel().addListSelectionListener(
135       new ListSelectionListener(){
136      public void valueChanged(ListSelectionEvent e){
137        resourcesListModel.dataChanged();
138      }
139     });
140     mainSplit.addComponentListener(new ComponentAdapter(){
141       public void componentShown(ComponentEvent e){
142         //try to honour left component's preferred size 
143         mainSplit.setDividerLocation(-100);
144       }
145     });
146   }
147   
148   protected Boolean getLoadNow(URL url){
149     Boolean res = (Boolean)loadNowByURL.get(url);
150     if(res == null){
151       res = new Boolean(Gate.getCreoleRegister().getDirectories().contains(url));
152       loadNowByURL.put(url, res);
153     }
154     return res;
155   }
156   
157   protected Boolean getLoadAlways(URL url){
158     Boolean res = (Boolean)loadAlwaysByURL.get(url);
159     if(res == null){
160       res = new Boolean(Gate.getAutoloadPlugins().contains(url));
161       loadAlwaysByURL.put(url, res);
162     }
163     return res;
164   }
165   
166   protected class MainTableModel extends AbstractTableModel{
167     public MainTableModel(){
168       localIcon = MainFrame.getIcon("loadFile.gif");
169       remoteIcon = MainFrame.getIcon("internet.gif");
170       invalidIcon = MainFrame.getIcon("param.gif");
171     }
172     public int getRowCount(){
173       return Gate.getKnownPlugins().size();
174     }
175     
176     public int getColumnCount(){
177       return 6;
178     }
179     
180     public String getColumnName(int column){
181       switch (column){
182         case NAME_COLUMN: return "Name";
183         case ICON_COLUMN: return "";
184         case URL_COLUMN: return "URL";
185         case LOAD_NOW_COLUMN: return "Load now";
186         case LOAD_ALWAYS_COLUMN: return "Load always";
187         case DELETE_COLUMN: return "Delete";
188         default: return "?";
189       }
190     }
191     
192     public Class getColumnClass(int columnIndex){
193       switch (columnIndex){
194         case NAME_COLUMN: return String.class;
195         case ICON_COLUMN: return Icon.class;
196         case URL_COLUMN: return String.class;
197         case LOAD_NOW_COLUMN: return Boolean.class;
198         case LOAD_ALWAYS_COLUMN: return Boolean.class;
199         case DELETE_COLUMN: return Object.class;
200         default: return Object.class;
201       }
202     }
203     
204     public Object getValueAt(int row, int column){
205       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
206               (URL)Gate.getKnownPlugins().get(row));
207       switch (column){
208         case NAME_COLUMN: return new File(dInfo.getUrl().getFile()).getName();
209         case ICON_COLUMN: return
210           dInfo.isValid() ? (
211             dInfo.getUrl().getProtocol().equalsIgnoreCase("file") ? 
212             localIcon : remoteIcon) :
213           invalidIcon;
214         case URL_COLUMN: return dInfo.getUrl().toString();
215         case LOAD_NOW_COLUMN: return  getLoadNow(dInfo.getUrl());
216         case LOAD_ALWAYS_COLUMN: return getLoadAlways(dInfo.getUrl());
217         case DELETE_COLUMN: return null;
218         default: return null;
219       }
220     }
221     
222     public boolean isCellEditable(int rowIndex, int columnIndex){
223       return columnIndex == LOAD_NOW_COLUMN || 
224         columnIndex == LOAD_ALWAYS_COLUMN ||
225         columnIndex == DELETE_COLUMN;
226     }
227     
228     public void setValueAt(Object aValue, int rowIndex, int columnIndex){
229       Boolean valueBoolean = (Boolean)aValue;
230       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
231               (URL)Gate.getKnownPlugins().get(rowIndex));
232       switch(columnIndex){
233         case LOAD_NOW_COLUMN: 
234           loadNowByURL.put(dInfo.getUrl(), valueBoolean);
235           break;
236         case LOAD_ALWAYS_COLUMN:
237           loadAlwaysByURL.put(dInfo.getUrl(), valueBoolean);
238           break;
239       }
240     }
241     
242     protected Icon localIcon;
243     protected Icon remoteIcon;
244     protected Icon invalidIcon;
245   }
246   
247   protected class ResourcesListModel extends AbstractListModel{
248     public Object getElementAt(int index){
249       int row = mainTable.getSelectedRow();
250       if(row == -1) return null;
251       row = mainTable.rowViewToModel(row);
252       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
253               (URL)Gate.getKnownPlugins().get(row));
254       return (Gate.ResourceInfo)dInfo.getResourceInfoList().get(index);
255     }
256     
257     public int getSize(){
258       
259       int row = mainTable.getSelectedRow();
260       if(row == -1) return 0;
261       row = mainTable.rowViewToModel(row);
262       Gate.DirectoryInfo dInfo = Gate.getDirectoryInfo(
263               (URL)Gate.getKnownPlugins().get(row));
264       return dInfo.getResourceInfoList().size();
265     }
266     
267     public void dataChanged(){
268 //      fireIntervalRemoved(this, 0, getSize() - 1);
269 //      fireIntervalAdded(this, 0, getSize() - 1);
270       fireContentsChanged(this, 0, getSize() - 1);
271     }
272   }
273   
274   /**
275    * This class acts both as cell renderer  and editor for all the cells in the 
276    * delete column.
277    */
278   protected class DeleteColumnCellRendererEditor extends AbstractCellEditor 
279     implements TableCellRenderer, TableCellEditor{
280     
281     public DeleteColumnCellRendererEditor(){
282       label = new JLabel();
283       rendererDeleteButton = new JButton(MainFrame.getIcon("delete.gif"));
284       rendererDeleteButton.setMaximumSize(rendererDeleteButton.getPreferredSize());
285       rendererDeleteButton.setMargin(new Insets(2, 5, 2, 5));
286       rendererBox = new JPanel();
287       rendererBox.setLayout(new GridBagLayout());
288       rendererBox.setOpaque(false);
289       GridBagConstraints constraints = new GridBagConstraints();
290       constraints.fill = GridBagConstraints.NONE;
291       constraints.gridy = 0;
292       constraints.gridx = GridBagConstraints.RELATIVE;
293       constraints.weightx = 1;
294       rendererBox.add(Box.createGlue(), constraints);
295       constraints.weightx = 0;
296       rendererBox.add(rendererDeleteButton, constraints);
297       constraints.weightx = 1;
298       rendererBox.add(Box.createGlue(), constraints);
299       
300       editorDeleteButton = new JButton(MainFrame.getIcon("delete.gif"));
301       editorDeleteButton.setMargin(new Insets(2, 5, 2, 5));
302       editorDeleteButton.addActionListener(new ActionListener(){
303         public void actionPerformed(ActionEvent evt){
304           int row = mainTable.getEditingRow();
305           // tell Swing that we aren't really editing this cell, otherwise an
306           // exception occurs when Swing tries to stop editing a cell that has
307           // been deleted.
308           TableCellEditor currentEditor = mainTable.getCellEditor();
309           if(currentEditor != null) {
310             currentEditor.cancelCellEditing();
311           }
312           row = mainTable.rowViewToModel(row);
313           URL toDelete = (URL)Gate.getKnownPlugins().get(row);
314           Gate.removeKnownPlugin(toDelete);
315           loadAlwaysByURL.remove(toDelete);
316           loadNowByURL.remove(toDelete);
317           mainTableModel.fireTableDataChanged();
318           resourcesListModel.dataChanged();
319         }
320       });
321     editorDeleteButton.setMaximumSize(editorDeleteButton.getPreferredSize());
322     editorBox = new JPanel();
323     editorBox.setLayout(new GridBagLayout());
324     editorBox.setOpaque(false);
325     constraints.weightx = 1;
326     editorBox.add(Box.createGlue(), constraints);
327     constraints.weightx = 0;
328     editorBox.add(editorDeleteButton, constraints);
329     constraints.weightx = 1;
330     editorBox.add(Box.createGlue(), constraints);
331     }
332     
333     public Component getTableCellRendererComponent(JTable table,
334             Object value,
335             boolean isSelected,
336             boolean hasFocus,
337             int row,
338             int column){
339 //      editorDeleteButton.setSelected(false);
340       switch(column){
341         case DELETE_COLUMN:
342 //          return rendererDeleteButton;
343           return rendererBox;
344         default: return null;
345       }
346     }
347     
348     public Component getTableCellEditorComponent(JTable table,
349             Object value,
350             boolean isSelected,
351             int row,
352             int column){
353       switch(column){
354         case DELETE_COLUMN:
355           return editorBox;
356         default: return null;
357       }
358     }
359     
360     public Object getCellEditorValue(){
361       return null;
362     }
363     
364     JButton editorDeleteButton;
365     JButton rendererDeleteButton;
366     JPanel rendererBox;
367     JPanel editorBox;
368     JLabel label;
369   }
370   
371   protected class ResourcesListCellRenderer extends DefaultListCellRenderer{
372     public Component getListCellRendererComponent(JList list,
373             Object value,
374             int index,
375             boolean isSelected,
376             boolean cellHasFocus){
377       Gate.ResourceInfo rInfo = (Gate.ResourceInfo)value;
378       //prepare the renderer
379       super.getListCellRendererComponent(list, 
380               rInfo.getResourceName(), index, isSelected, cellHasFocus);
381       //add tooltip text
382       setToolTipText(rInfo.getResourceComment());
383       return this;
384     }
385   }
386   
387   
388   protected class OkAction extends AbstractAction {
389     public OkAction(){
390       super("OK");
391     }
392     public void actionPerformed(ActionEvent evt){
393       setVisible(false);
394       //update the data structures to reflect the user's choices
395       Iterator pluginIter = loadNowByURL.keySet().iterator();
396       while(pluginIter.hasNext()){
397         URL aPluginURL = (URL)pluginIter.next();
398         boolean load = ((Boolean)loadNowByURL.get(aPluginURL)).booleanValue();
399         boolean loaded = Gate.getCreoleRegister().
400             getDirectories().contains(aPluginURL); 
401         if(load && !loaded){
402           //load the directory
403           try{
404             Gate.getCreoleRegister().registerDirectories(aPluginURL);
405           }catch(GateException ge){
406             throw new GateRuntimeException(ge);
407           }
408         }
409         if(!load && loaded){
410           //remove the directory
411           Gate.getCreoleRegister().removeDirectory(aPluginURL);
412         }
413       }
414       
415       
416       pluginIter = loadAlwaysByURL.keySet().iterator();
417       while(pluginIter.hasNext()){
418         URL aPluginURL = (URL)pluginIter.next();
419         boolean load = ((Boolean)loadAlwaysByURL.get(aPluginURL)).booleanValue();
420         boolean loaded = Gate.getAutoloadPlugins().contains(aPluginURL); 
421         if(load && !loaded){
422           //set autoload top true
423           Gate.addAutoloadPlugin(aPluginURL);
424         }
425         if(!load && loaded){
426           //set autoload to false
427           Gate.removeAutoloadPlugin(aPluginURL);
428         }
429       }
430       loadNowByURL.clear();
431       loadAlwaysByURL.clear();
432     }
433   }
434   
435   /**
436    * Overridden so we can populate the UI before showing.
437    */
438   public void setVisible(boolean visible){
439     if(visible){
440       loadNowByURL.clear();
441       loadAlwaysByURL.clear();      
442       mainTableModel.fireTableDataChanged();
443     }
444     super.setVisible(visible);
445   }
446   protected class CancelAction extends AbstractAction {
447     public CancelAction(){
448       super("Cancel");
449     }
450     
451     public void actionPerformed(ActionEvent evt){
452       setVisible(false);
453       loadNowByURL.clear();
454       loadAlwaysByURL.clear();      
455     }
456   }
457 
458   protected class AddCreoleRepositoryAction extends AbstractAction {
459     public AddCreoleRepositoryAction(){
460       super("Add a new CREOLE repository");
461       putValue(SHORT_DESCRIPTION,"Load a new CREOLE repository");
462     }
463 
464     public void actionPerformed(ActionEvent e) {
465       Box messageBox = Box.createHorizontalBox();
466       Box leftBox = Box.createVerticalBox();
467       JTextField urlTextField = new JTextField(20);
468       leftBox.add(new JLabel("Type an URL"));
469       leftBox.add(urlTextField);
470       messageBox.add(leftBox);
471 
472       messageBox.add(Box.createHorizontalStrut(10));
473       messageBox.add(new JLabel("or"));
474       messageBox.add(Box.createHorizontalStrut(10));
475 
476       class URLfromFileAction extends AbstractAction{
477         URLfromFileAction(JTextField textField){
478           super(null, MainFrame.getIcon("loadFile.gif"));
479           putValue(SHORT_DESCRIPTION,"Click to select a directory");
480           this.textField = textField;
481         }
482 
483         public void actionPerformed(ActionEvent e){
484           JFileChooser fileChooser = MainFrame.getFileChooser(); 
485           fileChooser.setMultiSelectionEnabled(false);
486           fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
487           fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
488           int result = fileChooser.showOpenDialog(PluginManagerUI.this);
489           if(result == JFileChooser.APPROVE_OPTION){
490             try{
491               textField.setText(fileChooser.getSelectedFile().
492                                             toURL().toExternalForm());
493             }catch(MalformedURLException mue){
494               throw new GateRuntimeException(mue.toString());
495             }
496           }
497         }
498         JTextField textField;
499       };//class URLfromFileAction extends AbstractAction
500 
501       Box rightBox = Box.createVerticalBox();
502       rightBox.add(new JLabel("Select a directory"));
503       JButton fileBtn = new JButton(new URLfromFileAction(urlTextField));
504       rightBox.add(fileBtn);
505       messageBox.add(rightBox);
506 
507       int res = JOptionPane.showOptionDialog(
508                             PluginManagerUI.this, messageBox,
509                             "Enter an URL to the directory containing the " +
510                             "\"creole.xml\" file", JOptionPane.OK_CANCEL_OPTION,
511                             JOptionPane.QUESTION_MESSAGE, null, null, null);
512       if(res == JOptionPane.OK_OPTION){
513         try{
514           URL creoleURL = new URL(urlTextField.getText());
515           Gate.addKnownPlugin(creoleURL);
516           mainTableModel.fireTableDataChanged();
517         }catch(Exception ex){
518           JOptionPane.showMessageDialog(
519               PluginManagerUI.this,
520               "There was a problem with your selection:\n" +
521               ex.toString() ,
522               "GATE", JOptionPane.ERROR_MESSAGE);
523           ex.printStackTrace(Err.getPrintWriter());
524         }
525       }
526     }
527   }//class LoadCreoleRepositoryAction extends AbstractAction
528 
529   protected XJTable mainTable;
530   protected JSplitPane mainSplit;
531   protected MainTableModel mainTableModel;
532   protected ResourcesListModel resourcesListModel;
533   protected JList resourcesList; 
534   
535   /**
536    * Map from URL to Boolean. Stores temporary values for the loadNow options.
537    */
538   protected Map loadNowByURL;
539   /**
540    * Map from URL to Boolean. Stores temporary values for the loadAlways 
541    * options.
542    */
543   protected Map loadAlwaysByURL;
544  
545   protected static final int ICON_COLUMN = 0;
546   protected static final int NAME_COLUMN = 1;
547   protected static final int URL_COLUMN = 2;
548   protected static final int LOAD_NOW_COLUMN = 3;
549   protected static final int LOAD_ALWAYS_COLUMN = 4;
550   protected static final int DELETE_COLUMN = 5;
551   
552 }
553