PluginUpdateManager.java
0001 /*
0002  * PluginUpdateManager.java
0003  *
0004  * Copyright (c) 2011, The University of Sheffield. See the file COPYRIGHT.txt
0005  * in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  * This file is part of GATE (see http://gate.ac.uk/), and is free software,
0008  * licenced under the GNU Library General Public License, Version 2, June 1991
0009  * (in the distribution as file licence.html, and also available at
0010  * http://gate.ac.uk/gate/licence.html).
0011  *
0012  * Mark A. Greenwood, 29/10/2011
0013  */
0014 
0015 package gate.gui.creole.manager;
0016 
0017 import gate.Gate;
0018 import gate.gui.MainFrame;
0019 import gate.resources.img.svg.AddIcon;
0020 import gate.resources.img.svg.AdvancedIcon;
0021 import gate.resources.img.svg.AvailableIcon;
0022 import gate.resources.img.svg.DownloadIcon;
0023 import gate.resources.img.svg.EditIcon;
0024 import gate.resources.img.svg.GATEUpdateSiteIcon;
0025 import gate.resources.img.svg.InvalidIcon;
0026 import gate.resources.img.svg.OpenFileIcon;
0027 import gate.resources.img.svg.RefreshIcon;
0028 import gate.resources.img.svg.RemoveIcon;
0029 import gate.resources.img.svg.UpdateSiteIcon;
0030 import gate.resources.img.svg.UpdatesIcon;
0031 import gate.swing.CheckBoxTableCellRenderer;
0032 import gate.swing.IconTableCellRenderer;
0033 import gate.swing.SpringUtilities;
0034 import gate.swing.XJFileChooser;
0035 import gate.swing.XJTable;
0036 import gate.util.Files;
0037 import gate.util.OptionsMap;
0038 import gate.util.VersionComparator;
0039 
0040 import java.awt.AWTException;
0041 import java.awt.BorderLayout;
0042 import java.awt.Component;
0043 import java.awt.Dimension;
0044 import java.awt.Robot;
0045 import java.awt.event.ActionEvent;
0046 import java.awt.event.ActionListener;
0047 import java.awt.event.InputEvent;
0048 import java.awt.event.KeyEvent;
0049 import java.awt.event.MouseAdapter;
0050 import java.awt.event.MouseEvent;
0051 import java.beans.PropertyChangeEvent;
0052 import java.beans.PropertyChangeListener;
0053 import java.io.File;
0054 import java.io.FileOutputStream;
0055 import java.io.IOException;
0056 import java.io.InputStream;
0057 import java.net.URI;
0058 import java.net.URISyntaxException;
0059 import java.net.URL;
0060 import java.net.URLConnection;
0061 import java.text.Collator;
0062 import java.util.ArrayList;
0063 import java.util.HashMap;
0064 import java.util.Iterator;
0065 import java.util.List;
0066 import java.util.Locale;
0067 import java.util.Map;
0068 import java.util.Set;
0069 
0070 import javax.swing.AbstractAction;
0071 import javax.swing.Action;
0072 import javax.swing.BorderFactory;
0073 import javax.swing.Box;
0074 import javax.swing.BoxLayout;
0075 import javax.swing.GrayFilter;
0076 import javax.swing.Icon;
0077 import javax.swing.ImageIcon;
0078 import javax.swing.JButton;
0079 import javax.swing.JCheckBox;
0080 import javax.swing.JComponent;
0081 import javax.swing.JDialog;
0082 import javax.swing.JFileChooser;
0083 import javax.swing.JFrame;
0084 import javax.swing.JLabel;
0085 import javax.swing.JOptionPane;
0086 import javax.swing.JPanel;
0087 import javax.swing.JScrollPane;
0088 import javax.swing.JTabbedPane;
0089 import javax.swing.JTextField;
0090 import javax.swing.JToolBar;
0091 import javax.swing.KeyStroke;
0092 import javax.swing.ListSelectionModel;
0093 import javax.swing.SpringLayout;
0094 import javax.swing.SwingUtilities;
0095 import javax.swing.event.ListSelectionEvent;
0096 import javax.swing.event.ListSelectionListener;
0097 import javax.swing.table.AbstractTableModel;
0098 
0099 import org.apache.tools.ant.Project;
0100 import org.apache.tools.ant.Target;
0101 import org.apache.tools.ant.taskdefs.Expand;
0102 
0103 /**
0104  * The CREOLE plugin manager which includes the ability to download and
0105  * install/update plugins from remote update sites.
0106  *
0107  @author Mark A. Greenwood
0108  */
0109 @SuppressWarnings("serial")
0110 public class PluginUpdateManager extends JDialog {
0111 
0112   private PluginTableModel availableModel = new PluginTableModel(3);
0113 
0114   private PluginTableModel updatesModel = new PluginTableModel(4);
0115 
0116   private UpdateSiteModel sitesModel = new UpdateSiteModel();
0117 
0118   private AvailablePlugins installed = new AvailablePlugins();
0119 
0120   private ProgressPanel progressPanel = new ProgressPanel();
0121 
0122   private JPanel panel = new JPanel(new BorderLayout());
0123 
0124   private JTabbedPane tabs = new JTabbedPane();
0125 
0126   private static File userPluginDir;
0127 
0128   private JFrame owner;
0129 
0130   private List<RemoteUpdateSite> updateSites =
0131       new ArrayList<RemoteUpdateSite>();
0132 
0133   private static final String GATE_USER_PLUGINS = "gate.user.plugins";
0134 
0135   private static final String GATE_UPDATE_SITES = "gate.update.sites";
0136 
0137   private static final String SUPPRESS_UPDATE_INSTALLED =
0138       "suppress.update.install";
0139 
0140   private static final String[] defaultUpdateSites = new String[]{
0141       "Additional Plugins from the GATE Team",
0142       "https://gate.ac.uk/gate/build/deploy/plugins/gate-8.1.xml",
0143       "Semantic Software Lab",
0144       "http://creole.semanticsoftware.info/gate-update-site.xml",
0145       //"City University Centre for Health Informatics",
0146       //"http://vega.soi.city.ac.uk/~abdy181/software/GATE/gate-update-site.xml",
0147       "Moonlytics",
0148       "http://word-correction-gate-plugin.googlecode.com/svn/trunk/site.xml",
0149       "SAGA",
0150       "http://demos.gsi.dit.upm.es/SAGA/gate-update-site.xml",
0151       "Austrian Research Institute for AI (OFAI)",
0152       "http://www.ofai.at/~johann.petrak/GATE/gate-update-site.xml",
0153       "BGLangTools",
0154       "http://www.grigoriliev.com/bglangtools/plugins/gate/gate-update-site.xml"};
0155 
0156   public static File getUserPluginsHome() {
0157     
0158     if(userPluginDir == null) {
0159       String upd = System.getProperty(GATE_USER_PLUGINS, Gate.getUserConfig().getString(GATE_USER_PLUGINS));
0160       if(upd != null) {
0161         userPluginDir = new File(upd);
0162         
0163         if(!userPluginDir.exists() || !userPluginDir.isDirectory() || !userPluginDir.canWrite()) {
0164           userPluginDir = null;
0165           Gate.getUserConfig().remove(GATE_USER_PLUGINS);
0166         }
0167       }
0168     }
0169     return userPluginDir;
0170   }
0171 
0172   /**
0173    * Responsible for pushing some of the config date for the plugin manager into
0174    * the main user config. Note that this doesn't actually persist the data,
0175    * that is only done on a clean exit of the GUI by code hidden somewhere else.
0176    */
0177   private void saveConfig() {
0178     Map<String, String> sites = new HashMap<String, String>();
0179     for(RemoteUpdateSite rus : updateSites) {
0180       sites.put(rus.uri.toString()(rus.enabled ? "1" "0"+ rus.name);
0181     }
0182     OptionsMap userConfig = Gate.getUserConfig();
0183     userConfig.put(GATE_UPDATE_SITES, sites);
0184     if(userPluginDir != null)
0185       userConfig.put(GATE_USER_PLUGINS, userPluginDir.getAbsolutePath());
0186   }
0187 
0188   /**
0189    * Load all the data about available plugins/updates from the remote update
0190    * sites as well as checking what is installed in the user plugin directory
0191    */
0192   private void loadData() {
0193     // display the progress panel to stop user input and show progress
0194     progressPanel.messageChanged("Loading CREOLE Plugin Information...");
0195     progressPanel.rangeChanged(00);
0196 
0197     if(getUserPluginsHome() == null) {
0198       // if the user plugin directory is not set then there is no point trying
0199       // to load any of the data, just disable the update/install tabs
0200       SwingUtilities.invokeLater(new Runnable() {
0201 
0202         @Override
0203         public void run() {
0204           // TODO Auto-generated method stub
0205           tabs.setEnabledAt(1false);
0206           tabs.setEnabledAt(2false);
0207           showProgressPanel(false);
0208         }
0209         
0210       });
0211       
0212       return;
0213     }
0214 
0215     // the assumption is that this code is run from the EDT so we need to run
0216     // the time consuming stuff in a different thread to stop things locking up
0217     new Thread() {
0218       @Override
0219       public void run() {
0220         // reset the info ready for a reload
0221         availableModel.data.clear();
0222         updatesModel.data.clear();
0223 
0224         // go through all the known update sites and get all the plugins they
0225         // are making available, skipping those sites which are marked as
0226         // invalid for some reason
0227         for(RemoteUpdateSite rus : updateSites) {
0228           if(rus.enabled && (rus.valid == null || rus.valid)) {
0229 
0230             for(CreolePlugin p : rus.getCreolePlugins()) {
0231               if(p != null) {
0232                 int index = availableModel.data.indexOf(p);
0233                 if(index == -1) {
0234                   availableModel.data.add(p);
0235                 else {
0236                   // if the plugin was already known then replace it if this
0237                   // instance is a newer version
0238                   CreolePlugin pp = availableModel.data.get(index);
0239 
0240                   if(VersionComparator.compareVersions(p.version, pp.version0) {
0241                     availableModel.data.remove(pp);
0242                     availableModel.data.add(p);
0243                   }
0244                 }
0245               }
0246             }
0247           }
0248         }
0249 
0250         // now work through the folders in the user plugin directory to see if
0251         // there are updates for any of the installed plugins
0252         if(userPluginDir.exists() && userPluginDir.isDirectory()) {
0253           File[] plugins = userPluginDir.listFiles();
0254           for(File f : plugins) {
0255             if(f.isDirectory()) {
0256               File pluginInfo = new File(f, "creole.xml");
0257               if(pluginInfo.exists()) {
0258                 try {
0259                   CreolePlugin plugin =
0260                       CreolePlugin.load(pluginInfo.toURI().toURL());
0261                   if(plugin != null) {
0262                     int index = availableModel.data.indexOf(plugin);
0263                     if(index != -1) {
0264                       CreolePlugin ap = availableModel.data.remove(index);
0265                       if(VersionComparator.compareVersions(ap.version,
0266                           plugin.version0) {
0267                         ap.installed = plugin.version;
0268                         ap.dir = f;
0269                         updatesModel.data.add(ap);
0270                       }
0271                     }
0272                   }
0273 
0274                   // add the plugin. most will already be known but this will
0275                   // catch any that have just been installed
0276                   Gate.addKnownPlugin(f.toURI().toURL());
0277                 catch(Exception e) {
0278                   e.printStackTrace();
0279                 }
0280               }
0281             }
0282           }
0283         }
0284 
0285         SwingUtilities.invokeLater(new Thread() {
0286           @Override
0287           public void run() {
0288             // update all the tables
0289             installed.reInit();
0290             updatesModel.dataChanged();
0291             availableModel.dataChanged();
0292             sitesModel.dataChanged();
0293 
0294             // enable the update tab if there are any
0295             tabs.setEnabledAt(1, updatesModel.data.size() 0);
0296             tabs.setEnabledAt(2true);
0297 
0298             // remove the progress panel
0299             showProgressPanel(false);
0300           }
0301         });
0302       }
0303     }.start();
0304   }
0305 
0306   private void showProgressPanel(final boolean visible) {
0307     if(visible == getRootPane().getGlassPane().isVisible()) return;
0308     if(visible) {
0309       remove(panel);
0310       add(progressPanel, BorderLayout.CENTER);
0311     else {
0312       remove(progressPanel);
0313       add(panel, BorderLayout.CENTER);
0314     }
0315     getRootPane().getGlassPane().setVisible(visible);
0316     validate();
0317   }
0318 
0319   private void applyChanges() {
0320     progressPanel.messageChanged("Updating CREOLE Plugin Configuration...");
0321     progressPanel.rangeChanged(0, updatesModel.data.size()
0322         + availableModel.data.size());
0323     showProgressPanel(true);
0324 
0325     // the assumption is that this code is run from the EDT so we need to run
0326     // the time consuming stuff in a different thread to stop things locking up
0327     new Thread() {
0328       @Override
0329       public void run() {
0330         if(getUserPluginsHome() != null) {
0331 
0332           // set up ANT ready to do the unzipping
0333           Expander expander = new Expander();
0334           expander.setOverwrite(true);
0335           expander.setDest(getUserPluginsHome());
0336 
0337           // store the list of failed plugins
0338           List<CreolePlugin> failed = new ArrayList<CreolePlugin>();
0339 
0340           // has the user been warned about installing updates (or have the
0341           // suppressed the warning)
0342           boolean hasBeenWarned =
0343               Gate.getUserConfig().getBoolean(SUPPRESS_UPDATE_INSTALLED);
0344 
0345           // lets start by going through the updates that are available
0346           Iterator<CreolePlugin> it = updatesModel.data.iterator();
0347           while(it.hasNext()) {
0348             CreolePlugin p = it.next();
0349             if(p.install) {
0350               // if the user wants the update...
0351               if(!hasBeenWarned) {
0352                 // warn them about the dangers of updating plugins if we haven't
0353                 // done so yet
0354                 if(JOptionPane
0355                     .showConfirmDialog(
0356                         PluginUpdateManager.this,
0357                         "<html><body style='width: 350px;'><b>UPDATE WARNING!</b><br><br>"
0358                             "Updating installed plugins will remove any customizations you may have made. "
0359                             "Are you sure you wish to continue?</body></html>",
0360                         "CREOLE Plugin Manager", JOptionPane.OK_CANCEL_OPTION,
0361                         JOptionPane.QUESTION_MESSAGE, new DownloadIcon(4848)) == JOptionPane.OK_OPTION) {
0362                   hasBeenWarned = true;
0363                 else {
0364                   // if they want to stop then remove the progress panel
0365                   SwingUtilities.invokeLater(new Thread() {
0366                     @Override
0367                     public void run() {
0368                       showProgressPanel(false);
0369                     }
0370                   });
0371                   return;
0372                 }
0373               }
0374 
0375               // report on which plugin we are updating
0376               progressPanel
0377                   .messageChanged("Updating CREOLE Plugin Configuration...<br>Currently Updating: "
0378                       + p.getName());
0379               try {
0380 
0381                 // download the new version
0382                 File downloaded = File.createTempFile("gate-plugin"".zip");
0383                 downloadFile(p.getName(), p.downloadURL, downloaded);
0384 
0385                 // try to rename the existing plugin folder
0386                 File renamed =
0387                     new File(getUserPluginsHome()"renamed-"
0388                         + System.currentTimeMillis());
0389 
0390                 if(!p.dir.renameTo(renamed)) {
0391                   // if we can't rename then just remember that we haven't
0392                   // updated this plugin
0393                   failed.add(p);
0394                 else {
0395                   // if we could rename then trash the old version
0396                   Files.rmdir(renamed);
0397 
0398                   // unzip the downloaded file
0399                   expander.setSrc(downloaded);
0400                   expander.execute();
0401 
0402                   // and delete the download
0403                   if(!downloaded.delete()) downloaded.deleteOnExit();
0404                 }
0405               catch(IOException ex) {
0406                 // something went wrong so log the failed plugin
0407                 ex.printStackTrace();
0408                 failed.add(p);
0409               }
0410             }
0411 
0412             // move on to the next plugin
0413             progressPanel.valueIncrement();
0414           }
0415 
0416           // now lets work through the available plugins
0417           it = availableModel.data.iterator();
0418           while(it.hasNext()) {
0419             CreolePlugin p = it.next();
0420             if(p.install) {
0421               // if plugin is marked for install then...
0422 
0423               // update the progress panel
0424               progressPanel
0425                   .messageChanged("Updating CREOLE Plugin Configuration...<br>Currently Installing: "
0426                       + p.getName());
0427               try {
0428                 // download the zip file
0429                 File downloaded = File.createTempFile("gate-plugin"".zip");
0430                 downloadFile(p.getName(), p.downloadURL, downloaded);
0431 
0432                 // unpack it into the right place
0433                 expander.setSrc(downloaded);
0434                 expander.execute();
0435 
0436                 // delete the download
0437                 if(!downloaded.delete()) downloaded.deleteOnExit();
0438               catch(IOException ex) {
0439                 // something went wrong so log the failed plugin
0440                 ex.printStackTrace();
0441                 failed.add(p);
0442               }
0443 
0444               // move on to the next plugin
0445               progressPanel.valueIncrement();
0446             }
0447           }
0448 
0449           // explain that some plugins failed to install
0450           if(failed.size() 0)
0451             JOptionPane
0452                 .showMessageDialog(
0453                     PluginUpdateManager.this,
0454                     "<html><body style='width: 350px;'><b>Installation of "
0455                         + failed.size()
0456                         " plugins failed!</b><br><br>"
0457                         "Try unloading all plugins and then restarting GATE before trying to install or update plugins.</body></html>",
0458                     PluginUpdateManager.this.getTitle(),
0459                     JOptionPane.ERROR_MESSAGE);
0460         }
0461 
0462         // (un)load already installed plugins
0463         progressPanel.messageChanged("Updating CREOLE Plugin Configuration...");
0464         
0465         Set<URL> failedPlugins = installed.updateAvailablePlugins();
0466         if (!failedPlugins.isEmpty()) {
0467           JOptionPane
0468           .showMessageDialog(
0469               PluginUpdateManager.this,
0470               "<html><body style='width: 350px;'><b>Loading of "
0471                   + failedPlugins.size()
0472                   " plugins failed!</b><br><br>"
0473                   "See the message pane for more details.</body></html>",
0474               PluginUpdateManager.this.getTitle(),
0475               JOptionPane.ERROR_MESSAGE);
0476         }
0477 
0478         // refresh the tables to reflect what we have just done
0479         loadData();
0480       }
0481     }.start();
0482   }
0483 
0484   @Override
0485   public void dispose() {
0486     MainFrame.getGuiRoots().remove(this);
0487     super.dispose();
0488   }
0489 
0490   public PluginUpdateManager(JFrame owner) {
0491     super(owner, true);
0492     this.owner = owner;
0493 
0494     // get the list of remote update sites so we can fill in the GUI
0495     Map<String, String> sites = Gate.getUserConfig().getMap(GATE_UPDATE_SITES);
0496     for(Map.Entry<String, String> site : sites.entrySet()) {
0497       try {
0498         updateSites.add(new RemoteUpdateSite(site.getValue().substring(1),
0499             new URI(site.getKey()), site.getValue().charAt(0== '1'));
0500       catch(URISyntaxException e) {
0501         e.printStackTrace();
0502       }
0503     }
0504 
0505     if(defaultUpdateSites.length % == 0) {
0506       // TODO the problem here is that we want to make sure new sites show up in
0507       // the list, but this means that if a user deletes a site it will respawn
0508       // next time they start GATE, not sure if there is a better solution.
0509 
0510       for(int i = 0; i < defaultUpdateSites.length; ++i) {
0511         try {
0512           RemoteUpdateSite rus =
0513               new RemoteUpdateSite(defaultUpdateSites[i]new URI(
0514                   defaultUpdateSites[++i])false);
0515 
0516           if(!updateSites.contains(rus)) updateSites.add(rus);
0517         catch(URISyntaxException e) {
0518           // this can never happen!
0519           e.printStackTrace();
0520         }
0521       }
0522     }
0523 
0524     // set up the main window
0525     setTitle("CREOLE Plugin Manager");
0526     setDefaultCloseOperation(DISPOSE_ON_CLOSE);
0527     setLayout(new BorderLayout());
0528     //setIconImages(Arrays.asList(new Image[]{new GATEIcon(64, 64).getImage(),
0529     //    new GATEIcon(48, 48).getImage(), new GATEIcon(32, 32).getImage(),
0530     //    new GATEIcon(22, 22).getImage(), new GATEIcon(16, 16).getImage()}));
0531 
0532     // set up the panel that displays the main GUI elements
0533     panel.setBorder(BorderFactory.createEmptyBorder(5555));
0534     panel.add(tabs, BorderLayout.CENTER);
0535 
0536     // initialize all the different tabs
0537     tabs.addTab("Installed Plugins"new AvailableIcon(2020), installed);
0538     tabs.addTab("Available Updates"new UpdatesIcon(2020), buildUpdates());
0539     tabs.addTab("Available to Install"new DownloadIcon(2020),
0540         buildAvailable());
0541     tabs.addTab("Configuration"new AdvancedIcon(2020), buildConfig());
0542     tabs.setDisabledIconAt(
0543         1,
0544         new ImageIcon(GrayFilter.createDisabledImage((new UpdatesIcon(2020))
0545             .getImage())));
0546     tabs.setDisabledIconAt(
0547         2,
0548         new ImageIcon(GrayFilter.createDisabledImage((new DownloadIcon(2020))
0549             .getImage())));
0550     tabs.setEnabledAt(1false);
0551     tabs.setEnabledAt(2false);
0552 
0553     // setup the row of buttons at the bottom of the screen...
0554     JPanel pnlButtons = new JPanel();
0555     pnlButtons.setLayout(new BoxLayout(pnlButtons, BoxLayout.X_AXIS));
0556     pnlButtons.setBorder(BorderFactory.createEmptyBorder(5000));
0557 
0558     // ... the apply button
0559     JButton btnApply = new JButton("Apply All");
0560     getRootPane().setDefaultButton(btnApply);
0561     btnApply.addActionListener(new ActionListener() {
0562       @Override
0563       public void actionPerformed(ActionEvent e) {
0564         PluginUpdateManager.this.applyChanges();
0565       }
0566     });
0567 
0568     // ... the close button
0569     Action cancelAction = new AbstractAction("Close") {
0570       @Override
0571       public void actionPerformed(ActionEvent e) {
0572 
0573         boolean changes = false;
0574 
0575         for(CreolePlugin p : availableModel.data) {
0576           changes = changes || p.install;
0577           if(changesbreak;
0578         }
0579 
0580         if(!changes) {
0581           for(CreolePlugin p : updatesModel.data) {
0582             changes = changes || p.install;
0583             if(changesbreak;
0584           }
0585         }
0586 
0587         if(!changeschanges = installed.unsavedChanges();
0588 
0589         if(changes
0590             && JOptionPane
0591                 .showConfirmDialog(
0592                     PluginUpdateManager.this,
0593                     "<html><body style='width: 350px;'><b>Changes Have Not Yet Been Applied!</b><br><br>"
0594                         "Would you like to apply your changes now?</body></html>",
0595                     "CREOLE Plugin Manager", JOptionPane.YES_NO_OPTION,
0596                     JOptionPane.QUESTION_MESSAGE== JOptionPane.OK_OPTION) {
0597           applyChanges();
0598         }
0599 
0600         PluginUpdateManager.this.setVisible(false);
0601       }
0602     };
0603     JButton btnCancel = new JButton(cancelAction);
0604 
0605     // ... and the help button
0606     Action helpAction = new AbstractAction("Help") {
0607       @Override
0608       public void actionPerformed(ActionEvent e) {
0609         MainFrame.getInstance().showHelpFrame("sec:howto:plugins",
0610             "gate.gui.creole.PluginUpdateManager");
0611       }
0612     };
0613     JButton btnHelp = new JButton(helpAction);
0614 
0615     // add the buttons to the panel
0616     pnlButtons.add(btnHelp);
0617     pnlButtons.add(Box.createHorizontalGlue());
0618     pnlButtons.add(btnApply);
0619     pnlButtons.add(Box.createHorizontalStrut(5));
0620     pnlButtons.add(btnCancel);
0621 
0622     // and the panel to the main GUI
0623     panel.add(pnlButtons, BorderLayout.SOUTH);
0624 
0625     // make the main GUI the currently visisble dialog item
0626     add(panel, BorderLayout.CENTER);
0627 
0628     // define keystrokes action bindings at the level of the main window
0629     getRootPane().registerKeyboardAction(cancelAction,
0630         KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
0631         JComponent.WHEN_IN_FOCUSED_WINDOW);
0632     getRootPane().registerKeyboardAction(helpAction,
0633         KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0),
0634         JComponent.WHEN_IN_FOCUSED_WINDOW);
0635 
0636     // make sure the dialog is a reasonable size
0637     pack();
0638     Dimension screenSize = getGraphicsConfiguration().getBounds().getSize();
0639     Dimension dialogSize = getPreferredSize();
0640     int width =
0641         dialogSize.width > screenSize.width
0642             ? screenSize.width * 4
0643             : dialogSize.width;
0644     int height =
0645         dialogSize.height > screenSize.height
0646             ? screenSize.height * 3
0647             : dialogSize.height;
0648     setSize(width, height);
0649     validate();
0650 
0651     // place the dialog somewhere sensible
0652     setLocationRelativeTo(owner);
0653   }
0654 
0655   private Component buildUpdates() {
0656     XJTable tblUpdates = new XJTable(updatesModel);
0657     tblUpdates.getColumnModel().getColumn(0)
0658         .setCellRenderer(new CheckBoxTableCellRenderer());
0659     tblUpdates.setSortable(false);
0660     tblUpdates.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
0661     tblUpdates.getColumnModel().getColumn(0).setMaxWidth(100);
0662     tblUpdates.getColumnModel().getColumn(2).setMaxWidth(100);
0663     tblUpdates.getColumnModel().getColumn(3).setMaxWidth(100);
0664 
0665     tblUpdates.getColumnModel().getColumn(1)
0666         .setCellEditor(new JTextPaneTableCellRenderer());
0667 
0668     tblUpdates.setSortable(true);
0669     tblUpdates.setSortedColumn(1);
0670     Collator collator = Collator.getInstance(Locale.ENGLISH);
0671     collator.setStrength(Collator.TERTIARY);
0672     tblUpdates.setComparator(1, collator);
0673 
0674     JScrollPane scroller = new JScrollPane(tblUpdates);
0675     scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
0676     return scroller;
0677   }
0678 
0679   private Component buildAvailable() {
0680     final XJTable tblAvailable = new XJTable();
0681     tblAvailable.setModel(availableModel);
0682 
0683     tblAvailable.getColumnModel().getColumn(0)
0684         .setCellRenderer(new CheckBoxTableCellRenderer());
0685     tblAvailable.setSortable(false);
0686     tblAvailable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
0687     tblAvailable.getColumnModel().getColumn(0).setMaxWidth(100);
0688     tblAvailable.getColumnModel().getColumn(2).setMaxWidth(100);
0689 
0690     tblAvailable.getColumnModel().getColumn(1)
0691         .setCellEditor(new JTextPaneTableCellRenderer());
0692 
0693     tblAvailable.addMouseListener(new MouseAdapter() {
0694       @Override
0695       public void mousePressed(MouseEvent e) {
0696         process(e);
0697       }
0698 
0699       @Override
0700       public void mouseReleased(MouseEvent e) {
0701         process(e);
0702       }
0703 
0704       @Override
0705       public void mouseClicked(MouseEvent e) {
0706         process(e);
0707       }
0708 
0709       private void process(final MouseEvent e) {
0710         //final int row = tblAvailable.rowAtPoint(e.getPoint());
0711         final int column = tblAvailable.columnAtPoint(e.getPoint());
0712         if (column == 1) {
0713           SwingUtilities.invokeLater(new Runnable() {
0714 
0715             @Override
0716             public void run() {
0717               try {
0718                 Robot robot = new Robot();
0719                 if (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_CLICKEDrobot.mousePress(InputEvent.BUTTON1_MASK);
0720                 if (e.getID() == MouseEvent.MOUSE_RELEASED || e.getID() == MouseEvent.MOUSE_CLICKEDrobot.mouseRelease(InputEvent.BUTTON1_MASK);
0721               catch(AWTException e) {
0722                 e.printStackTrace();
0723               }
0724             }
0725           });
0726         }
0727       }
0728     });
0729 
0730     tblAvailable.setSortable(true);
0731     tblAvailable.setSortedColumn(1);
0732     Collator collator = Collator.getInstance(Locale.ENGLISH);
0733     collator.setStrength(Collator.TERTIARY);
0734     tblAvailable.setComparator(1, collator);
0735 
0736     JScrollPane scrollerAvailable = new JScrollPane(tblAvailable);
0737     scrollerAvailable
0738         .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
0739 
0740     return scrollerAvailable;
0741   }
0742 
0743   private Component buildConfig() {
0744 
0745     // the main update site area
0746     JPanel pnlUpdateSites = new JPanel(new BorderLayout());
0747     pnlUpdateSites.setBorder(BorderFactory
0748         .createTitledBorder("Plugin Repositories:"));
0749     final XJTable tblSites = new XJTable(sitesModel);
0750     tblSites.getColumnModel().getColumn(0)
0751         .setCellRenderer(new IconTableCellRenderer());
0752     tblSites.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
0753 
0754     JScrollPane scroller = new JScrollPane(tblSites);
0755     scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
0756     pnlUpdateSites.add(scroller, BorderLayout.CENTER);
0757     final JPanel pnlEdit = new JPanel(new SpringLayout());
0758     final JTextField txtName = new JTextField(20);
0759     final JTextField txtURL = new JTextField(20);
0760     pnlEdit.add(new JLabel("Name: "));
0761     pnlEdit.add(txtName);
0762     pnlEdit.add(new JLabel("URL: "));
0763     pnlEdit.add(txtURL);
0764     SpringUtilities.makeCompactGrid(pnlEdit, 226666);
0765     JButton btnAdd = new JButton(new AddIcon(2424));
0766     btnAdd.addActionListener(new ActionListener() {
0767       @Override
0768       public void actionPerformed(ActionEvent e) {
0769         txtName.setText("");
0770         txtURL.setText("");
0771 
0772         final JOptionPane options =
0773             new JOptionPane(pnlEdit, JOptionPane.QUESTION_MESSAGE,
0774                 JOptionPane.OK_CANCEL_OPTION, new UpdateSiteIcon(4848));
0775         final JDialog dialog =
0776             new JDialog(PluginUpdateManager.this, "Plugin Repository Info",
0777                 true);
0778         options.addPropertyChangeListener(new PropertyChangeListener() {
0779           @Override
0780           public void propertyChange(PropertyChangeEvent e) {
0781             if (options.getValue().equals(JOptionPane.UNINITIALIZED_VALUE)) return;
0782             String prop = e.getPropertyName();
0783             if(dialog.isVisible() && (e.getSource() == options)
0784                 && (prop.equals(JOptionPane.VALUE_PROPERTY))) {
0785               if(((Integer)options.getValue()).intValue() == JOptionPane.OK_OPTION) {
0786                 if(txtName.getText().trim().equals("")) {
0787                   txtName.requestFocusInWindow();
0788                   options.setValue(JOptionPane.UNINITIALIZED_VALUE);
0789                   return;
0790                 }
0791                 if(txtURL.getText().trim().equals("")) {
0792                   txtURL.requestFocusInWindow();
0793                   options.setValue(JOptionPane.UNINITIALIZED_VALUE);
0794                   return;
0795                 }
0796               }
0797               dialog.setVisible(false);
0798             }
0799           }
0800         });
0801 
0802         dialog.setContentPane(options);
0803         dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
0804         dialog.pack();
0805         dialog.setLocationRelativeTo(PluginUpdateManager.this);
0806         dialog.setVisible(true);
0807 
0808         if(((Integer)options.getValue()).intValue() != JOptionPane.OK_OPTION)
0809           return;
0810         if(txtName.getText().trim().equals("")) return;
0811         if(txtURL.getText().trim().equals("")) return;
0812 
0813         dialog.dispose();
0814 
0815         try {
0816           updateSites.add(new RemoteUpdateSite(txtName.getText().trim(),
0817               new URI(txtURL.getText().trim())true));
0818           showProgressPanel(true);
0819           saveConfig();
0820           loadData();
0821         catch(Exception ex) {
0822           ex.printStackTrace();
0823         }
0824       }
0825     });
0826 
0827     final JButton btnRemove = new JButton(new RemoveIcon(2424));
0828     btnRemove.setEnabled(false);
0829     btnRemove.setDisabledIcon(new ImageIcon(GrayFilter
0830         .createDisabledImage((new RemoveIcon(2424)).getImage())));
0831     btnRemove.addActionListener(new ActionListener() {
0832       @Override
0833       public void actionPerformed(ActionEvent e) {
0834         showProgressPanel(true);
0835         int row = tblSites.getSelectedRow();
0836         if(row == -1return;
0837         row = tblSites.rowViewToModel(row);
0838         updateSites.remove(row);
0839         saveConfig();
0840         loadData();
0841       }
0842     });
0843 
0844     final JButton btnEdit = new JButton(new EditIcon(2424));
0845     btnEdit.setDisabledIcon(new ImageIcon(GrayFilter
0846         .createDisabledImage((new EditIcon(2424)).getImage())));
0847     btnEdit.setEnabled(false);
0848     btnEdit.addActionListener(new ActionListener() {
0849       @Override
0850       public void actionPerformed(ActionEvent e) {
0851         int row = tblSites.getSelectedRow();
0852         if(row == -1return;
0853         row = tblSites.rowViewToModel(row);
0854         RemoteUpdateSite site = updateSites.get(row);
0855         txtName.setText(site.name);
0856         txtURL.setText(site.uri.toString());
0857         if(JOptionPane.showConfirmDialog(PluginUpdateManager.this, pnlEdit,
0858             "Update Site Info", JOptionPane.OK_CANCEL_OPTION,
0859             JOptionPane.QUESTION_MESSAGE, new UpdateSiteIcon(4848)) != JOptionPane.OK_OPTION)
0860           return;
0861         if(txtName.getText().trim().equals("")) return;
0862         if(txtURL.getText().trim().equals("")) return;
0863         try {
0864           URI url = new URI(txtURL.getText().trim());
0865           if(!url.equals(site.uri)) {
0866             site.uri = url;
0867             site.plugins = null;
0868           }
0869           site.name = txtName.getText().trim();
0870           site.valid = null;
0871           showProgressPanel(true);
0872           saveConfig();
0873           loadData();
0874 
0875         catch(Exception ex) {
0876           ex.printStackTrace();
0877         }
0878       }
0879     });
0880 
0881     final JButton btnRefresh = new JButton(new RefreshIcon(2424));
0882     btnRefresh.setDisabledIcon(new ImageIcon(GrayFilter
0883         .createDisabledImage((new RefreshIcon(2424)).getImage())));
0884     btnRefresh.setEnabled(false);
0885     btnRefresh.addActionListener(new ActionListener() {
0886       @Override
0887       public void actionPerformed(ActionEvent arg0) {
0888         int row = tblSites.getSelectedRow();
0889         if(row == -1return;
0890         row = tblSites.rowViewToModel(row);
0891         RemoteUpdateSite site = updateSites.get(row);
0892         site.plugins = null;
0893         showProgressPanel(true);
0894         saveConfig();
0895         loadData();
0896       }
0897     });
0898 
0899     tblSites.getSelectionModel().addListSelectionListener(
0900         new ListSelectionListener() {
0901           @Override
0902           public void valueChanged(ListSelectionEvent e) {
0903             if(e.getValueIsAdjusting()) return;
0904 
0905             boolean enable = (tblSites.getSelectedRow() != -1);
0906             btnRemove.setEnabled(enable);
0907             btnEdit.setEnabled(enable);
0908             btnRefresh.setEnabled(enable);
0909           }
0910         });
0911 
0912     JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);
0913     toolbar.setFloatable(false);
0914     toolbar.add(btnAdd);
0915     toolbar.add(btnRemove);
0916     toolbar.add(btnRefresh);
0917     toolbar.add(btnEdit);
0918     pnlUpdateSites.add(toolbar, BorderLayout.EAST);
0919 
0920     // the user plugin dir area
0921     JToolBar pnlUserPlugins = new JToolBar(JToolBar.HORIZONTAL);
0922     pnlUserPlugins.setOpaque(false);
0923     pnlUserPlugins.setFloatable(false);
0924     pnlUserPlugins.setLayout(new BoxLayout(pnlUserPlugins, BoxLayout.X_AXIS));
0925     pnlUserPlugins.setBorder(BorderFactory.createEmptyBorder(2222));
0926 
0927     //String userPluginsDir = (String)Gate.getUserConfig().get(GATE_USER_PLUGINS);
0928     getUserPluginsHome();
0929     final JTextField txtUserPlugins =
0930         new JTextField(userPluginDir == null "" : userPluginDir.getAbsolutePath());
0931     txtUserPlugins.setEditable(false);
0932 
0933     JButton btnUserPlugins = new JButton(new OpenFileIcon(2424));
0934     btnUserPlugins.addActionListener(new ActionListener() {
0935       @Override
0936       public void actionPerformed(ActionEvent e) {
0937         XJFileChooser fileChooser = MainFrame.getFileChooser();
0938         fileChooser.setMultiSelectionEnabled(false);
0939         fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
0940         fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
0941         fileChooser.setResource(GATE_USER_PLUGINS);
0942 
0943         if(fileChooser.showOpenDialog(PluginUpdateManager.this== JFileChooser.APPROVE_OPTION) {
0944           userPluginDir = fileChooser.getSelectedFile();
0945 
0946           if(!userPluginDir.exists()) {
0947             JOptionPane
0948                 .showMessageDialog(
0949                     owner,
0950                     "<html><body style='width: 350px;'><b>Selected Folder Doesn't Exist!</b><br><br>"
0951                         "In order to install new CREOLE plugins you must choose a user plugins folder, "
0952                         "which exists and is writable.",
0953                     "CREOLE Plugin Manager", JOptionPane.ERROR_MESSAGE);
0954             return;
0955           }
0956 
0957           if(!userPluginDir.isDirectory()) {
0958             JOptionPane
0959                 .showMessageDialog(
0960                     owner,
0961                     "<html><body style='width: 350px;'><b>You Selected A File Instead Of A Folder!</b><br><br>"
0962                         "In order to install new CREOLE plugins you must choose a user plugins folder, "
0963                         "which exists and is writable.",
0964                     "CREOLE Plugin Manager", JOptionPane.ERROR_MESSAGE);
0965             return;
0966           }
0967 
0968           if(!userPluginDir.canWrite()) {
0969             JOptionPane
0970                 .showMessageDialog(
0971                     owner,
0972                     "<html><body style='width: 350px;'><b>Selected Folder Is Read Only!</b><br><br>"
0973                         "In order to install new CREOLE plugins you must choose a user plugins folder, "
0974                         "which exists and is writable.",
0975                     "CREOLE Plugin Manager", JOptionPane.ERROR_MESSAGE);
0976             return;
0977           }
0978 
0979           txtUserPlugins.setText(userPluginDir.getAbsolutePath());
0980           saveConfig();
0981           loadData();
0982         }
0983       }
0984     });
0985     pnlUserPlugins.setBorder(BorderFactory
0986         .createTitledBorder("User Plugin Directory: "));
0987     pnlUserPlugins.add(txtUserPlugins);
0988     pnlUserPlugins.add(btnUserPlugins);
0989 
0990     // the suppress warnings area
0991     JPanel pnlSuppress = new JPanel();
0992     pnlSuppress.setLayout(new BoxLayout(pnlSuppress, BoxLayout.X_AXIS));
0993     pnlSuppress.setBorder(BorderFactory
0994         .createTitledBorder("Suppress Warning Messages:"));
0995     //final JCheckBox chkUserPlugins =
0996     //    new JCheckBox("User Plugin Directory Not Set", Gate.getUserConfig()
0997     //        .getBoolean(SUPPRESS_USER_PLUGINS));
0998     //pnlSuppress.add(chkUserPlugins);
0999     //pnlSuppress.add(Box.createHorizontalStrut(10));
1000     final JCheckBox chkUpdateInsatlled =
1001         new JCheckBox("Update Of Installed Plugin", Gate.getUserConfig()
1002             .getBoolean(SUPPRESS_UPDATE_INSTALLED));
1003     pnlSuppress.add(chkUpdateInsatlled);
1004     ActionListener chkListener = new ActionListener() {
1005       @Override
1006       public void actionPerformed(ActionEvent arg0) {
1007         //Gate.getUserConfig().put(SUPPRESS_USER_PLUGINS,
1008         //    chkUserPlugins.isSelected());
1009         Gate.getUserConfig().put(SUPPRESS_UPDATE_INSTALLED,
1010             chkUpdateInsatlled.isSelected());
1011       }
1012     };
1013     chkUpdateInsatlled.addActionListener(chkListener);
1014     //chkUserPlugins.addActionListener(chkListener);
1015 
1016     // assemble the full panel and return it
1017     JPanel panel = new JPanel(new BorderLayout());
1018     panel.add(pnlUpdateSites, BorderLayout.CENTER);
1019     panel.add(pnlUserPlugins, BorderLayout.NORTH);
1020     panel.add(pnlSuppress, BorderLayout.SOUTH);
1021     panel.setBorder(BorderFactory.createEmptyBorder(5555));
1022     return panel;
1023   }
1024 
1025   /**
1026    * Download a file from a URL into a local file while updating the progress
1027    * panel so the user knows how far through we are
1028    *
1029    @param name
1030    *          the name of the plugin for the progress feedback
1031    @param url
1032    *          the URL to download from
1033    @param file
1034    *          the file to save into
1035    @throws IOException
1036    *           if any IO related problems occur
1037    */
1038   private void downloadFile(String name, URL url, File filethrows IOException {
1039     InputStream in = null;
1040     FileOutputStream out = null;
1041 
1042     try {
1043       // get a connection to the URL
1044       URLConnection conn = url.openConnection();
1045       conn.setConnectTimeout(10000);
1046       conn.setReadTimeout(10000);
1047 
1048       // use this to configure the progress info
1049       int expectedSize = conn.getContentLength();
1050       progressPanel.downloadStarting(name, expectedSize == -1);
1051       int downloaded = 0;
1052 
1053       // create a 1KB buffer to speed up downloaded
1054       byte[] buf = new byte[1024];
1055 
1056       // records how much of the buffer was filled
1057       int length;
1058 
1059       // open the input and output streams
1060       in = conn.getInputStream();
1061       out = new FileOutputStream(file);
1062 
1063       // keep filling the buffer and then writing it out to the file until there
1064       // is no more data, all the time keep updating the progress bar
1065       while((in != null&& ((length = in.read(buf)) != -1)) {
1066         downloaded += length;
1067         out.write(buf, 0, length);
1068         if(expectedSize != -1)
1069           progressPanel.downloadProgress((downloaded * 100/ expectedSize);
1070       }
1071 
1072       // flush the output file to ensure everything is written to disk
1073       out.flush();
1074     finally {
1075       // once we have finished close all the streams and report that we are done
1076       progressPanel.downloadFinished();
1077       if(out != nullout.close();
1078       if(in != nullin.close();
1079     }
1080   }
1081 
1082   @Override
1083   public void setVisible(boolean visible) {
1084     if(visible) {
1085       MainFrame.getGuiRoots().add(this);
1086       // if the window is about to be made visible then do some quick setup
1087       tabs.setSelectedIndex(0);
1088       installed.reInit();
1089       loadData();
1090 
1091       // warn the user if their plugni dir isn't set
1092       /*if(userPluginDir == null
1093           && !Gate.getUserConfig().getBoolean(SUPPRESS_USER_PLUGINS)) {
1094         JOptionPane
1095             .showMessageDialog(
1096                 owner,
1097                 "<html><body style='width: 350px;'><b>The user plugin folder has not yet been configured!</b><br><br>"
1098                     + "In order to install new CREOLE plugins you must choose a user plugins folder. "
1099                     + "This can be achieved from the Configuration tab of the CREOLE Plugin Manager.",
1100                 "CREOLE Plugin Manager", JOptionPane.INFORMATION_MESSAGE,
1101                 new UserPluginIcon(48, 48));
1102       }*/
1103     }
1104 
1105     // now actually show/hide the window
1106     super.setVisible(visible);
1107 
1108     dispose();
1109   }
1110 
1111   private static class PluginTableModel extends AbstractTableModel {
1112     private int columns;
1113 
1114     private List<CreolePlugin> data = new ArrayList<CreolePlugin>();
1115 
1116     public PluginTableModel(int columns) {
1117       this.columns = columns;
1118     }
1119 
1120     @Override
1121     public int getColumnCount() {
1122       return columns;
1123     }
1124 
1125     @Override
1126     public int getRowCount() {
1127       return data.size();
1128     }
1129 
1130     @Override
1131     public Object getValueAt(int row, int column) {
1132       CreolePlugin plugin = data.get(row);
1133       switch(column){
1134         case 0:
1135           return plugin.install;
1136         case 1:
1137           return "<html><body>"
1138               (plugin.getHelpURL() != null
1139                   "<a href=\"" + plugin.getHelpURL() "\">"
1140                       + plugin.getName() "</a>"
1141                   : plugin.getName())
1142               + plugin.compatabilityInfo()
1143               (plugin.description != null
1144                   "<br><span style='font-size: 80%;'>" + plugin.description
1145                       "</span>"
1146                   """</body></html>";
1147         case 2:
1148           return plugin.version;
1149         case 3:
1150           return plugin.installed;
1151         default:
1152           return null;
1153       }
1154     }
1155 
1156     @Override
1157     public boolean isCellEditable(int row, int column) {
1158       if(column > 1return false;
1159       return data.get(row).compatible;
1160     }
1161 
1162     @Override
1163     public void setValueAt(Object value, int row, int column) {
1164       if(column != 0return;
1165       CreolePlugin plugin = data.get(row);
1166       plugin.install = (Boolean)value;
1167     }
1168 
1169     @Override
1170     public String getColumnName(int column) {
1171       switch(column){
1172         case 0:
1173           return "<html><body style='padding: 2px; text-align: center;'>Install</body></html>";
1174         case 1:
1175           return "<html><body style='padding: 2px; text-align: center;'>Plugin Name</body></html>";
1176         case 2:
1177           // TODO it would be nice to use "Version<br>Available" but for some
1178           // reason the header isn't expanding
1179           return "<html><body style='padding: 2px; text-align: center;'>Available</body></html>";
1180         case 3:
1181           return "<html><body style='padding: 2px; text-align: center;'>Installed</body></html>";
1182         default:
1183           return null;
1184       }
1185     }
1186 
1187     @Override
1188     public Class<?> getColumnClass(int column) {
1189       switch(column){
1190         case 0:
1191           return Boolean.class;
1192         case 1:
1193           return String.class;
1194         case 2:
1195           return String.class;
1196         case 3:
1197           return String.class;
1198         default:
1199           return null;
1200       }
1201     }
1202 
1203     public void dataChanged() {
1204       fireTableDataChanged();
1205     }
1206   }
1207 
1208   private static class Expander extends Expand {
1209     public Expander() {
1210       setProject(new Project());
1211       getProject().init();
1212       setTaskType("unzip");
1213       setTaskName("unzip");
1214       setOwningTarget(new Target());
1215     }
1216   }
1217 
1218   private class UpdateSiteModel extends AbstractTableModel {
1219     private transient Icon icoSite = new UpdateSiteIcon(3232);
1220 
1221     private transient Icon icoInvalid = new InvalidIcon(3232);
1222 
1223     private transient Icon icoGATE = new GATEUpdateSiteIcon(3232);
1224 
1225     @Override
1226     public String getColumnName(int column) {
1227       switch(column){
1228         case 0:
1229           return "";
1230         case 1:
1231           return "<html><body style='padding: 2px; text-align: center;'>Enabled</body></html>";
1232         case 2:
1233           return "<html><body style='padding: 2px; text-align: center;'>Repository Info</body></html>";
1234         default:
1235           return null;
1236       }
1237     }
1238 
1239     @Override
1240     public Class<?> getColumnClass(int column) {
1241       switch(column){
1242         case 0:
1243           return Icon.class;
1244         case 1:
1245           return Boolean.class;
1246         case 2:
1247           return String.class;
1248         default:
1249           return null;
1250       }
1251     }
1252 
1253     @Override
1254     public boolean isCellEditable(int row, int column) {
1255       return column == 1;
1256     }
1257 
1258     @Override
1259     public void setValueAt(Object value, int row, int column) {
1260       RemoteUpdateSite site = updateSites.get(row);
1261       site.enabled = (Boolean)value;
1262       saveConfig();
1263 
1264       if(site.enabled) {
1265         // TODO can we do this without a complete reload?
1266         showProgressPanel(true);
1267         loadData();
1268       else {
1269         availableModel.data.removeAll(site.getCreolePlugins());
1270         updatesModel.data.removeAll(site.getCreolePlugins());
1271         availableModel.dataChanged();
1272         updatesModel.dataChanged();
1273         tabs.setEnabledAt(1, updatesModel.getRowCount() 0);
1274       }
1275     }
1276 
1277     public void dataChanged() {
1278       fireTableDataChanged();
1279     }
1280 
1281     @Override
1282     public int getColumnCount() {
1283       return 3;
1284     }
1285 
1286     @Override
1287     public int getRowCount() {
1288       return updateSites.size();
1289     }
1290 
1291     @Override
1292     public Object getValueAt(int row, int column) {
1293       RemoteUpdateSite site = updateSites.get(row);
1294       switch(column){
1295         case 0:
1296           if(site.valid != null && !site.validreturn icoInvalid;
1297           if(site.uri.toString().startsWith("https://gate.ac.uk"))
1298             return icoGATE;
1299           return icoSite;
1300         case 1:
1301           return site.enabled;
1302         case 2:
1303           return "<html><body>" + site.name
1304               "<br><span style='font-size: 80%;'>" + site.uri
1305               "</span></body></html>";
1306         default:
1307           return null;
1308       }
1309     }
1310   }
1311 }