PageRenderTime 189ms CodeModel.GetById 101ms app.highlight 73ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/pluginmgr/InstallPanel.java

#
Java | 1319 lines | 1087 code | 135 blank | 97 comment | 164 complexity | 62e080c19f453e994b6db702d62b0b28 MD5 | raw file
   1/*
   2 * InstallPanel.java - For installing plugins
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 2002 Kris Kopicki
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License
  10 * as published by the Free Software Foundation; either version 2
  11 * of the License, or any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  21 */
  22
  23package org.gjt.sp.jedit.pluginmgr;
  24
  25//{{{ Imports
  26import org.gjt.sp.jedit.*;
  27import org.gjt.sp.jedit.browser.VFSBrowser;
  28import org.gjt.sp.jedit.gui.RolloverButton;
  29import org.gjt.sp.jedit.io.VFS;
  30import org.gjt.sp.jedit.io.VFSManager;
  31import org.gjt.sp.util.Log;
  32import org.gjt.sp.util.StandardUtilities;
  33import org.gjt.sp.util.ThreadUtilities;
  34import org.gjt.sp.util.XMLUtilities;
  35import org.xml.sax.Attributes;
  36import org.xml.sax.SAXException;
  37import org.xml.sax.helpers.DefaultHandler;
  38
  39import javax.swing.*;
  40import javax.swing.border.EmptyBorder;
  41import javax.swing.event.ListSelectionEvent;
  42import javax.swing.event.ListSelectionListener;
  43import javax.swing.event.DocumentEvent;
  44import javax.swing.event.DocumentListener;
  45import javax.swing.event.TableModelEvent;
  46import javax.swing.event.TableModelListener;
  47import javax.swing.table.AbstractTableModel;
  48import javax.swing.table.DefaultTableCellRenderer;
  49import javax.swing.table.JTableHeader;
  50import javax.swing.table.TableColumn;
  51import javax.swing.text.html.HTMLEditorKit;
  52import java.awt.*;
  53import java.awt.event.*;
  54import java.io.File;
  55import java.io.InputStream;
  56import java.text.NumberFormat;
  57import java.text.ParseException;
  58import java.text.SimpleDateFormat;
  59import java.util.*;
  60import java.util.List;
  61//}}}
  62
  63/**
  64 * @version $Id: InstallPanel.java 19765 2011-08-08 12:41:01Z kpouer $
  65 */
  66class InstallPanel extends JPanel implements EBComponent
  67{
  68
  69	//{{{ Variables
  70	private final JTable table;
  71	private final JScrollPane scrollpane;
  72	private final PluginTableModel pluginModel;
  73	private final PluginManager window;
  74	private final PluginInfoBox infoBox;
  75	private final ChoosePluginSet chooseButton;
  76	private final boolean updates;
  77
  78	private final Collection<String> pluginSet = new HashSet<String>();
  79	//}}}
  80
  81	//{{{ InstallPanel constructor
  82	InstallPanel(PluginManager window, boolean updates)
  83	{
  84		super(new BorderLayout(12,12));
  85
  86		this.window = window;
  87		this.updates = updates;
  88
  89		setBorder(new EmptyBorder(12,12,12,12));
  90
  91		final JSplitPane split = new JSplitPane(
  92			JSplitPane.VERTICAL_SPLIT, jEdit.getBooleanProperty("appearance.continuousLayout"));
  93		split.setResizeWeight(0.75);
  94		/* Setup the table */
  95		table = new JTable(pluginModel = new PluginTableModel());
  96		table.setShowGrid(false);
  97		table.setIntercellSpacing(new Dimension(0,0));
  98		table.setRowHeight(table.getRowHeight() + 2);
  99		table.setPreferredScrollableViewportSize(new Dimension(500,200));
 100		table.setDefaultRenderer(Object.class, new TextRenderer(
 101			(DefaultTableCellRenderer)table.getDefaultRenderer(Object.class)));
 102		table.addFocusListener(new TableFocusHandler());
 103		InputMap tableInputMap = table.getInputMap(JComponent.WHEN_FOCUSED);
 104		ActionMap tableActionMap = table.getActionMap();
 105		tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,0),"tabOutForward");
 106		tableActionMap.put("tabOutForward",new KeyboardAction(KeyboardCommand.TAB_OUT_FORWARD));
 107		tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,InputEvent.SHIFT_MASK),"tabOutBack");
 108		tableActionMap.put("tabOutBack",new KeyboardAction(KeyboardCommand.TAB_OUT_BACK));
 109		tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0),"editPlugin");
 110		tableActionMap.put("editPlugin",new KeyboardAction(KeyboardCommand.EDIT_PLUGIN));
 111		tableInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0),"closePluginManager");
 112		tableActionMap.put("closePluginManager",new KeyboardAction(KeyboardCommand.CLOSE_PLUGIN_MANAGER));
 113
 114		TableColumn col1 = table.getColumnModel().getColumn(0);
 115		TableColumn col2 = table.getColumnModel().getColumn(1);
 116		TableColumn col3 = table.getColumnModel().getColumn(2);
 117		TableColumn col4 = table.getColumnModel().getColumn(3);
 118		TableColumn col5 = table.getColumnModel().getColumn(4);
 119
 120		col1.setPreferredWidth(30);
 121		col1.setMinWidth(30);
 122		col1.setMaxWidth(30);
 123		col1.setResizable(false);
 124
 125		col2.setPreferredWidth(180);
 126		col3.setPreferredWidth(130);
 127		col4.setPreferredWidth(70);
 128		col5.setPreferredWidth(70);
 129
 130		JTableHeader header = table.getTableHeader();
 131		header.setReorderingAllowed(false);
 132		header.addMouseListener(new HeaderMouseHandler());
 133		header.setDefaultRenderer(new HeaderRenderer(
 134			(DefaultTableCellRenderer)header.getDefaultRenderer()));
 135
 136		scrollpane = new JScrollPane(table);
 137		scrollpane.getViewport().setBackground(table.getBackground());
 138		split.setTopComponent(scrollpane);
 139
 140		/* Create description */
 141		JScrollPane infoPane = new JScrollPane(
 142			infoBox = new PluginInfoBox());
 143		infoPane.setPreferredSize(new Dimension(500,100));
 144		split.setBottomComponent(infoPane);
 145
 146		EventQueue.invokeLater(new Runnable()
 147		{
 148			@Override
 149			public void run()
 150			{
 151				split.setDividerLocation(0.75);
 152			}
 153		});
 154
 155		final JTextField searchField = new JTextField();
 156		searchField.addKeyListener(new KeyAdapter()
 157		{
 158			@Override
 159			public void keyPressed(KeyEvent e)
 160			{
 161				if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP)
 162				{
 163					table.dispatchEvent(e);
 164					table.requestFocus();
 165				}
 166			}
 167		});
 168		searchField.getDocument().addDocumentListener(new DocumentListener()
 169		{
 170			void update()
 171			{
 172				pluginModel.setFilterString(searchField.getText());
 173			}
 174
 175			@Override
 176			public void changedUpdate(DocumentEvent e)
 177			{
 178				update();
 179			}
 180
 181			@Override
 182			public void insertUpdate(DocumentEvent e)
 183			{
 184				update();
 185			}
 186
 187			@Override
 188			public void removeUpdate(DocumentEvent e)
 189			{
 190				update();
 191			}
 192		});
 193		table.addKeyListener(new KeyAdapter()
 194		{
 195			@Override
 196			public void keyPressed(KeyEvent e)
 197			{
 198				int i = table.getSelectedRow(), n = table.getModel().getRowCount();
 199				if (e.getKeyCode() == KeyEvent.VK_DOWN && i == (n - 1) ||
 200					e.getKeyCode() == KeyEvent.VK_UP && i == 0) 
 201				{
 202					searchField.requestFocus();
 203					searchField.selectAll();
 204				}
 205			}
 206		});
 207		Box filterBox = Box.createHorizontalBox();
 208		filterBox.add(new JLabel("Filter : "));
 209		filterBox.add(searchField);
 210		add(BorderLayout.NORTH,filterBox);
 211		add(BorderLayout.CENTER,split);
 212
 213		/* Create buttons */
 214		Box buttons = new Box(BoxLayout.X_AXIS);
 215
 216		buttons.add(new InstallButton());
 217		buttons.add(Box.createHorizontalStrut(12));
 218		buttons.add(new SelectallButton());
 219		buttons.add(chooseButton = new ChoosePluginSet());
 220		buttons.add(new ClearPluginSet());
 221		buttons.add(Box.createGlue());
 222		buttons.add(new SizeLabel());
 223
 224
 225		add(BorderLayout.SOUTH,buttons);
 226		String path = jEdit.getProperty(PluginManager.PROPERTY_PLUGINSET, "");
 227		if (!path.isEmpty())
 228		{
 229			loadPluginSet(path);
 230		}
 231	} //}}}
 232
 233	//{{{ loadPluginSet() method
 234	/** loads a pluginSet xml file and updates the model to reflect
 235	    certain checked selections
 236	    @since jEdit 4.3pre10
 237	    @author Alan Ezust
 238	*/
 239	boolean loadPluginSet(String path)
 240	{
 241		pluginSet.clear();
 242		pluginModel.restoreSelection(new HashSet<String>(), new HashSet<String>());
 243
 244		VFS vfs = VFSManager.getVFSForPath(path);
 245		Object session = vfs.createVFSSession(path, InstallPanel.this);
 246		try
 247		{
 248			InputStream is = vfs._createInputStream(session, path, false, InstallPanel.this);
 249			XMLUtilities.parseXML(is, new StringMapHandler());
 250		}
 251		catch (Exception e)
 252		{
 253			Log.log(Log.WARNING, this, "Loading Pluginset failed:" + e.getMessage());
 254			return false;
 255		}
 256		pluginModel.update();
 257		return true;
 258	} //}}}
 259
 260	//{{{ updateModel() method
 261	public void updateModel()
 262	{
 263		final Set<String> savedChecked = new HashSet<String>();
 264		final Set<String> savedSelection = new HashSet<String>();
 265		pluginModel.saveSelection(savedChecked, savedSelection);
 266		pluginModel.clear();
 267		infoBox.setText(jEdit.getProperty("plugin-manager.list-download"));
 268
 269		ThreadUtilities.runInDispatchThread(new Runnable()
 270		{
 271			@Override
 272			public void run()
 273			{
 274				infoBox.setText(null);
 275				pluginModel.update();
 276				pluginModel.restoreSelection(savedChecked, savedSelection);
 277			}
 278		});
 279	} //}}}
 280
 281	//{{{ handleMessage() method
 282	@Override
 283	public void handleMessage(EBMessage message)
 284	{
 285		 if (message.getSource() == PluginManager.getInstance())
 286		 {
 287			 chooseButton.path = jEdit.getProperty(PluginManager.PROPERTY_PLUGINSET, "");
 288			 if (chooseButton.path.length() > 0)
 289			 {
 290				 loadPluginSet(chooseButton.path);
 291				 pluginModel.restoreSelection(new HashSet<String>(), new HashSet<String>());
 292				 chooseButton.updateUI();
 293			 }
 294		}
 295	} //}}}
 296
 297	//{{{ Private members
 298
 299	//{{{ formatSize() method
 300	private static String formatSize(int size)
 301	{
 302		NumberFormat df = NumberFormat.getInstance();
 303		df.setMaximumFractionDigits(1);
 304		df.setMinimumFractionDigits(0);
 305		String sizeText;
 306		if (size < 1048576)
 307			sizeText = (size >> 10) + "KB";
 308		else
 309			sizeText = df.format(size/ 1048576.0d) + "MB";
 310		return sizeText;
 311	} //}}}
 312
 313	//}}}
 314
 315	//{{{ Inner classes
 316
 317	//{{{ PluginTableModel class
 318	private class PluginTableModel extends AbstractTableModel
 319	{
 320		/** This List can contain String or Entry. */
 321		private final List entries = new ArrayList();
 322		private final List filteredEntries = new ArrayList();
 323		private int sortType = EntryCompare.COLUMN_NAME;
 324		private String filterString;
 325		int sortDirection = 1;
 326		
 327		//{{{ setFilterString() method
 328		public void setFilterString(String filterString)
 329		{
 330			this.filterString = filterString;
 331			updateFilteredEntries();
 332		} //}}}
 333		
 334		//{{{ updateFilteredEntries() method
 335		void updateFilteredEntries()
 336		{
 337			filteredEntries.clear();
 338			if (filterString == null)
 339				filteredEntries.addAll(entries);
 340			else
 341			{
 342				String[] words = filterString.toLowerCase().split("\\s+");
 343				for (Object o : entries)
 344				{
 345					if (!(o instanceof Entry))
 346						continue;
 347					Entry e = (Entry)o;
 348
 349					if (e.install)
 350					{
 351						filteredEntries.add(e);
 352						continue;
 353					}
 354
 355					
 356					String s = (e.name + ' ' + e.set + ' ' + e.description).toLowerCase();
 357					boolean hasAll = true;
 358					boolean negative = false;
 359					for (String word : words)
 360					{
 361						if (word.startsWith("-"))
 362						{
 363							word = word.substring(1);
 364							negative = true;
 365						}
 366						if (word.length() == 0)
 367							continue;
 368						
 369						if (negative == s.contains(word))
 370						{
 371							hasAll = false;
 372							break;
 373						}
 374						negative = false;
 375					}
 376					if (hasAll)
 377						filteredEntries.add(e);
 378				}
 379			}
 380			fireTableChanged(new TableModelEvent(PluginTableModel.this));
 381		} //}}}
 382
 383		//{{{ getColumnClass() method
 384		@Override
 385		public Class getColumnClass(int columnIndex)
 386		{
 387			switch (columnIndex)
 388			{
 389				case 0: return Boolean.class;
 390				case 1:
 391				case 2:
 392				case 3:
 393				case 4:
 394				case 5: return Object.class;
 395				default: throw new Error("Column out of range");
 396			}
 397		} //}}}
 398
 399		//{{{ getColumnCount() method
 400		@Override
 401		public int getColumnCount()
 402		{
 403			return 6;
 404		} //}}}
 405
 406		//{{{ getColumnName() method
 407		@Override
 408		public String getColumnName(int column)
 409		{
 410			switch (column)
 411			{
 412				case 0: return " ";
 413				case 1: return ' '+jEdit.getProperty("install-plugins.info.name");
 414				case 2: return ' '+jEdit.getProperty("install-plugins.info.category");
 415				case 3: return ' '+jEdit.getProperty("install-plugins.info.version");
 416				case 4: return ' '+jEdit.getProperty("install-plugins.info.size");
 417				case 5: return ' '+jEdit.getProperty("install-plugins.info.releaseDate");
 418				default: throw new Error("Column out of range");
 419			}
 420		} //}}}
 421
 422		//{{{ getRowCount() method
 423		@Override
 424		public int getRowCount()
 425		{
 426			return filteredEntries.size();
 427		} //}}}
 428
 429		//{{{ getValueAt() method
 430		@Override
 431		public Object getValueAt(int rowIndex,int columnIndex)
 432		{
 433			Object obj = filteredEntries.get(rowIndex);
 434			if(obj instanceof String)
 435			{
 436				if(columnIndex == 1)
 437					return obj;
 438				else
 439					return null;
 440			}
 441			else
 442			{
 443				Entry entry = (Entry)obj;
 444
 445				switch (columnIndex)
 446				{
 447					case 0:
 448						return entry.install;
 449					case 1:
 450						return entry.name;
 451					case 2:
 452						return entry.set;
 453					case 3:
 454						if (updates)
 455							return entry.installedVersion + "->" + entry.version;
 456						return entry.version;
 457					case 4:
 458						return formatSize(entry.size);
 459					case 5:
 460						return entry.date;
 461					default:
 462						throw new Error("Column out of range");
 463				}
 464			}
 465		} //}}}
 466
 467		//{{{ isCellEditable() method
 468		@Override
 469		public boolean isCellEditable(int rowIndex, int columnIndex)
 470		{
 471			return columnIndex == 0;
 472		} //}}}
 473
 474		//{{{ setSelectAll() method
 475		public void setSelectAll(boolean b)
 476		{
 477			if(isDownloadingList())
 478				return;
 479
 480			int length = getRowCount();
 481			for (int i = 0; i < length; i++)
 482			{
 483				if (b)
 484					setValueAt(Boolean.TRUE,i,0);
 485				else
 486				{
 487					Entry entry = (Entry)filteredEntries.get(i);
 488					entry.parents = new LinkedList<Entry>();
 489					entry.install = false;
 490				}
 491			}
 492			fireTableChanged(new TableModelEvent(this));
 493		} //}}}
 494
 495		//{{{ setSortType() method
 496		public void setSortType(int type)
 497		{
 498			sortType = type;
 499			sort(type);
 500		} //}}}
 501
 502		//{{{ deselectParents() method
 503		private void deselectParents(Entry entry)
 504		{
 505			Entry[] parents = entry.getParents();
 506
 507			if (parents.length == 0)
 508				return;
 509
 510			String[] args = { entry.name };
 511			int result = GUIUtilities.listConfirm(
 512				window,"plugin-manager.dependency",
 513				args,parents);
 514			if (result != JOptionPane.OK_OPTION)
 515			{
 516				entry.install = true;
 517				return;
 518			}
 519
 520			for(int i = 0; i < parents.length; i++)
 521				 parents[i].install = false;
 522
 523			fireTableRowsUpdated(0,getRowCount() - 1);
 524		} //}}}
 525
 526		//{{{ setValueAt() method
 527		@Override
 528		public void setValueAt(Object aValue, int row, int column)
 529		{
 530			if (column != 0) return;
 531
 532			Object obj = filteredEntries.get(row);
 533			if(obj instanceof String)
 534				return;
 535
 536			Entry entry = (Entry)obj;
 537			boolean before = entry.install;
 538			entry.install = Boolean.TRUE.equals(aValue);
 539			if (before == entry.install) return;
 540			if (!entry.install)
 541				deselectParents(entry);
 542
 543			List<PluginList.Dependency> deps = entry.plugin.getCompatibleBranch().deps;
 544
 545			for (int i = 0; i < deps.size(); i++)
 546			{
 547				PluginList.Dependency dep = deps.get(i);
 548				if ("plugin".equals(dep.what))
 549				{
 550					boolean found = false;
 551					for (int j = 0; j < filteredEntries.size(); j++)
 552					{
 553						Entry temp = (Entry)filteredEntries.get(j);
 554						if (temp.plugin == dep.plugin)
 555						{
 556							found = true;
 557							if (entry.install)
 558							{
 559								temp.parents.add(entry);
 560								setValueAt(Boolean.TRUE,j,0);
 561							}
 562							else
 563								temp.parents.remove(entry);
 564
 565							break;
 566						}
 567					}
 568					if (!found)
 569					{
 570						// the dependency was not found in the filtered list so we search in
 571						// global list.
 572						for (int a = 0;a<entries.size();a++)
 573						{
 574							Entry temp = (Entry) entries.get(a);
 575							if (temp.plugin == dep.plugin)
 576							{
 577								if (entry.install)
 578								{
 579									temp.parents.add(entry);
 580									temp.install = true;
 581								}
 582								else
 583									temp.parents.remove(entry);
 584								break;
 585							}
 586						}
 587					}
 588				}
 589			}
 590			updateFilteredEntries();
 591		} //}}}
 592
 593		//{{{ sort() method
 594		public void sort(int type)
 595		{
 596			Set<String> savedChecked = new HashSet<String>();
 597			Set<String> savedSelection = new HashSet<String>();
 598			saveSelection(savedChecked,savedSelection);
 599
 600			if (sortType != type)
 601			{
 602				sortDirection = 1;
 603			}
 604			sortType = type;
 605
 606			if(isDownloadingList())
 607				return;
 608
 609			Collections.sort(entries,new EntryCompare(type, sortDirection));
 610			updateFilteredEntries();
 611			restoreSelection(savedChecked,savedSelection);
 612			table.getTableHeader().repaint();
 613		}
 614		//}}}
 615
 616		//{{{ isDownloadingList() method
 617		private boolean isDownloadingList()
 618		{
 619			return entries.size() == 1 && entries.get(0) instanceof String;
 620		} //}}}
 621
 622		//{{{ clear() method
 623		public void clear()
 624		{
 625			entries.clear();
 626			updateFilteredEntries();
 627		} //}}}
 628
 629		//{{{ update() method
 630		public void update()
 631		{
 632			Set<String> savedChecked = new HashSet<String>();
 633			Set<String> savedSelection = new HashSet<String>();
 634			saveSelection(savedChecked,savedSelection);
 635
 636			PluginList pluginList = window.getPluginList();
 637
 638			if (pluginList == null) return;
 639
 640			entries.clear();
 641			
 642			for(int i = 0; i < pluginList.pluginSets.size(); i++)
 643			{
 644				PluginList.PluginSet set = pluginList.pluginSets.get(i);
 645				for(int j = 0; j < set.plugins.size(); j++)
 646				{
 647					PluginList.Plugin plugin = pluginList.pluginHash.get(set.plugins.get(j));
 648					PluginList.Branch branch = plugin.getCompatibleBranch();
 649					String installedVersion =
 650						plugin.getInstalledVersion();
 651					if (updates)
 652					{
 653						if(branch != null
 654							&& branch.canSatisfyDependencies()
 655							&& installedVersion != null
 656							&& StandardUtilities.compareStrings(branch.version,
 657							installedVersion,false) > 0)
 658						{
 659							entries.add(new Entry(plugin, set.name));
 660						}
 661					}
 662					else
 663					{
 664						if(installedVersion == null && plugin.canBeInstalled())
 665							entries.add(new Entry(plugin,set.name));
 666					}
 667				}
 668			}
 669
 670			sort(sortType);
 671			updateFilteredEntries();
 672			restoreSelection(savedChecked, savedSelection);
 673		} //}}}
 674
 675		//{{{ saveSelection() method
 676		public void saveSelection(Set<String> savedChecked, Set<String> savedSelection)
 677		{
 678			if (entries.isEmpty())
 679				return;
 680			for (int i=0, c=getRowCount() ; i<c ; i++)
 681			{
 682				if ((Boolean)getValueAt(i,0))
 683				{
 684					savedChecked.add(filteredEntries.get(i).toString());
 685				}
 686			}
 687			int[] rows = table.getSelectedRows();
 688			for (int i=0 ; i<rows.length ; i++)
 689			{
 690				savedSelection.add(filteredEntries.get(rows[i]).toString());
 691			}
 692		} //}}}
 693
 694		//{{{ restoreSelection() method
 695		public void restoreSelection(Set<String> savedChecked, Set<String> savedSelection)
 696		{
 697			for (int i=0, c=getRowCount() ; i<c ; i++)
 698			{
 699				Object obj = filteredEntries.get(i);
 700				String name = obj.toString();
 701				if (obj instanceof Entry)
 702				{
 703					name = ((Entry)obj).plugin.jar;
 704				}
 705				if (pluginSet.contains(name))
 706					setValueAt(true, i, 0);
 707				else setValueAt(savedChecked.contains(name), i, 0);
 708			}
 709			if (table == null) return;
 710			
 711			table.setColumnSelectionInterval(0,0);
 712			if (!savedSelection.isEmpty())
 713			{
 714				int i = 0;
 715				int rowCount = getRowCount();
 716				for ( ; i<rowCount ; i++)
 717				{
 718					String name = filteredEntries.get(i).toString();
 719					if (savedSelection.contains(name))
 720					{
 721						table.setRowSelectionInterval(i,i);
 722						break;
 723					}
 724				}
 725				ListSelectionModel lsm = table.getSelectionModel();
 726				for ( ; i<rowCount ; i++)
 727				{
 728					String name = filteredEntries.get(i).toString();
 729					if (savedSelection.contains(name))
 730					{
 731						lsm.addSelectionInterval(i,i);
 732					}
 733				}
 734			}
 735			else
 736			{
 737				if (table.getRowCount() != 0)
 738					table.setRowSelectionInterval(0,0);
 739				JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
 740				scrollbar.setValue(scrollbar.getMinimum());
 741			}
 742		} //}}}
 743	} //}}}
 744
 745	//{{{ Entry class
 746	private static class Entry
 747	{
 748		String name, installedVersion, version, author, date, description, set;
 749
 750		long timestamp;
 751		int size;
 752		boolean install;
 753		PluginList.Plugin plugin;
 754		List<Entry> parents = new LinkedList<Entry>();
 755
 756		Entry(PluginList.Plugin plugin, String set)
 757		{
 758			PluginList.Branch branch = plugin.getCompatibleBranch();
 759			boolean downloadSource = jEdit.getBooleanProperty("plugin-manager.downloadSource");
 760			int size = downloadSource ? branch.downloadSourceSize : branch.downloadSize;
 761
 762			this.name = plugin.name;
 763			this.author = plugin.author;
 764			this.installedVersion = plugin.getInstalledVersion();
 765			this.version = branch.version;
 766			this.size = size;
 767			this.date = branch.date;
 768			this.description = plugin.description;
 769			this.set = set;
 770			this.install = false;
 771			this.plugin = plugin;
 772			SimpleDateFormat format = new SimpleDateFormat("d MMMM yyyy", Locale.ENGLISH);
 773			try
 774			{
 775				timestamp = format.parse(date).getTime();
 776			}
 777			catch (ParseException e)
 778			{
 779				Log.log(Log.ERROR, this, e);
 780			}
 781		}
 782
 783		private void getParents(List<Entry> list)
 784		{
 785			for (Entry entry : parents)
 786			{
 787				if (entry.install && !list.contains(entry))
 788				{
 789					list.add(entry);
 790					entry.getParents(list);
 791				}
 792			}
 793		}
 794
 795		Entry[] getParents()
 796		{
 797			List<Entry> list = new ArrayList<Entry>();
 798			getParents(list);
 799			Entry[] array = list.toArray(new Entry[list.size()]);
 800			Arrays.sort(array,new StandardUtilities.StringCompare<Entry>(true));
 801			return array;
 802		}
 803
 804		@Override
 805		public String toString()
 806		{
 807			return name;
 808		}
 809	} //}}}
 810
 811	//{{{ PluginInfoBox class
 812	/**
 813	 * @TODO refactor to use the PluginDetailPanel?
 814	 */
 815	private class PluginInfoBox extends JTextPane implements ListSelectionListener
 816	{
 817		private final String[] params;
 818		PluginInfoBox()
 819		{
 820			setBackground(jEdit.getColorProperty("view.bgColor"));
 821			setForeground(jEdit.getColorProperty("view.fgColor"));
 822			putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
 823			setEditable(false);
 824			setEditorKit(new HTMLEditorKit());
 825//			setLineWrap(true);
 826//			setWrapStyleWord(true);
 827			params = new String[3];
 828			table.getSelectionModel().addListSelectionListener(this);
 829		}
 830
 831
 832		@Override
 833		public void valueChanged(ListSelectionEvent e)
 834		{
 835			String text = "";
 836			if (table.getSelectedRowCount() == 1)
 837			{
 838				Entry entry = (Entry) pluginModel.filteredEntries
 839					.get(table.getSelectedRow());
 840				params[0] = entry.author;
 841				params[1] = entry.date;
 842				params[2] = entry.description;
 843				text = jEdit.getProperty("install-plugins.info", params);
 844				text = text.replace("\n", "<br>");
 845				text = "<html>" + text + "</html>";
 846			}
 847			setText(text);
 848			setCaretPosition(0);
 849		}
 850	} //}}}
 851
 852	//{{{ SizeLabel class
 853	private class SizeLabel extends JLabel implements TableModelListener
 854	{
 855		private int size;
 856		private int nbPlugins;
 857
 858		SizeLabel()
 859		{
 860			update();
 861			pluginModel.addTableModelListener(this);
 862		}
 863
 864		@Override
 865		public void tableChanged(TableModelEvent e)
 866		{
 867			if (e.getType() == TableModelEvent.UPDATE)
 868			{
 869				if(pluginModel.isDownloadingList())
 870					return;
 871
 872				size = 0;
 873				nbPlugins = 0;
 874				int length = pluginModel.entries.size();
 875				for (int i = 0; i < length; i++)
 876				{
 877					Entry entry = (Entry)pluginModel
 878						.entries.get(i);
 879					if (entry.install)
 880					{
 881						nbPlugins++;
 882						size += entry.size;
 883					}
 884				}
 885				update();
 886			}
 887		}
 888
 889		private void update()
 890		{
 891			Object[] args = {nbPlugins, formatSize(size)};
 892			setText(jEdit.getProperty("install-plugins.totalSize", args));
 893		}
 894	} //}}}
 895
 896	//{{{ SelectallButton class
 897	private class SelectallButton extends JCheckBox implements ActionListener, TableModelListener
 898	{
 899		SelectallButton()
 900		{
 901			super(jEdit.getProperty("install-plugins.select-all"));
 902			addActionListener(this);
 903			pluginModel.addTableModelListener(this);
 904			setEnabled(false);
 905		}
 906
 907		@Override
 908		public void actionPerformed(ActionEvent evt)
 909		{
 910			pluginModel.setSelectAll(isSelected());
 911		}
 912
 913		@Override
 914		public void tableChanged(TableModelEvent e)
 915		{
 916			if(pluginModel.isDownloadingList())
 917				return;
 918
 919			setEnabled(pluginModel.getRowCount() != 0);
 920			if (e.getType() == TableModelEvent.UPDATE)
 921			{
 922				int length = pluginModel.getRowCount();
 923				for (int i = 0; i < length; i++)
 924					if (!((Boolean)pluginModel.getValueAt(i,0)).booleanValue())
 925					{
 926						setSelected(false);
 927						return;
 928					}
 929				if (length > 0)
 930					setSelected(true);
 931			}
 932		}
 933	} //}}}
 934
 935	//{{{ StringMapHandler class
 936	/** For parsing the pluginset xml files into pluginSet */
 937	private class StringMapHandler extends DefaultHandler
 938	{
 939		StringMapHandler()
 940		{
 941			pluginSet.clear();
 942		}
 943
 944		@Override
 945		public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
 946		{
 947			if ("plugin".equals(localName))
 948			{
 949				pluginSet.add(attrs.getValue("jar"));
 950			}
 951		}
 952	} //}}}
 953
 954	//{{{ ChoosePluginSet class
 955	private class ChoosePluginSet extends RolloverButton implements ActionListener
 956	{
 957		private String path;
 958
 959		//{{{ ChoosePluginSet constructor
 960		ChoosePluginSet()
 961		{
 962			setIcon(GUIUtilities.loadIcon(jEdit.getProperty("install-plugins.choose-plugin-set.icon")));
 963			addActionListener(this);
 964			updateUI();
 965		} //}}}
 966
 967		//{{{ updateUI method
 968		@Override
 969		public void updateUI()
 970		{
 971			path = jEdit.getProperty(PluginManager.PROPERTY_PLUGINSET, "");
 972			if (path.length()<1) setToolTipText ("Click here to choose a predefined plugin set");
 973			else setToolTipText ("Choose pluginset (" + path + ')');
 974			super.updateUI();
 975		}//}}}
 976
 977		//{{{ actionPerformed() method
 978		@Override
 979		public void actionPerformed(ActionEvent ae)
 980		{
 981			
 982			path = jEdit.getProperty(PluginManager.PROPERTY_PLUGINSET,
 983				jEdit.getSettingsDirectory() + File.separator);
 984			String[] selectedFiles = GUIUtilities.showVFSFileDialog(InstallPanel.this.window,
 985				jEdit.getActiveView(), path, VFSBrowser.OPEN_DIALOG, false);
 986			if (selectedFiles == null || selectedFiles.length != 1) return;
 987
 988			path = selectedFiles[0];
 989			boolean success = loadPluginSet(path);
 990			if (success)
 991			{
 992				jEdit.setProperty(PluginManager.PROPERTY_PLUGINSET, path);
 993			}
 994			updateUI();
 995		} //}}}
 996	}//}}}
 997
 998	//{{{ ClearPluginSet class
 999	private class ClearPluginSet extends RolloverButton implements ActionListener
1000	{
1001		//{{{ ClearPluginSet constructor
1002		ClearPluginSet()
1003		{
1004			setIcon(GUIUtilities.loadIcon(jEdit.getProperty("install-plugins.clear-plugin-set.icon")));
1005			setToolTipText("clear plugin set");
1006			addActionListener(this);
1007		} //}}}
1008
1009		//{{{ actionPerformed() method
1010		@Override
1011		public void actionPerformed(ActionEvent e)
1012		{
1013			pluginSet.clear();
1014			pluginModel.restoreSelection(new HashSet<String>(), new HashSet<String>());
1015			jEdit.unsetProperty(PluginManager.PROPERTY_PLUGINSET);
1016			chooseButton.updateUI();
1017		} //}}}
1018	} //}}}
1019
1020	//{{{ InstallButton class
1021	private class InstallButton extends JButton implements ActionListener, TableModelListener
1022	{
1023		InstallButton()
1024		{
1025			super(jEdit.getProperty("install-plugins.install"));
1026			pluginModel.addTableModelListener(this);
1027			addActionListener(this);
1028			setEnabled(false);
1029		}
1030
1031		@Override
1032		public void actionPerformed(ActionEvent evt)
1033		{
1034			if(pluginModel.isDownloadingList())
1035				return;
1036
1037			boolean downloadSource = jEdit.getBooleanProperty(
1038				"plugin-manager.downloadSource");
1039			boolean installUser = jEdit.getBooleanProperty(
1040				"plugin-manager.installUser");
1041			Roster roster = new Roster();
1042			String installDirectory;
1043			if(installUser)
1044			{
1045				installDirectory = MiscUtilities.constructPath(
1046					jEdit.getSettingsDirectory(),"jars");
1047			}
1048			else
1049			{
1050				installDirectory = MiscUtilities.constructPath(
1051					jEdit.getJEditHome(),"jars");
1052			}
1053
1054			int length = pluginModel.entries.size();
1055			int instcount = 0;
1056			for (int i = 0; i < length; i++)
1057			{
1058				Entry entry = (Entry)pluginModel.entries.get(i);
1059				if (entry.install)
1060				{
1061					entry.plugin.install(roster,installDirectory,downloadSource);
1062					if (updates)
1063						entry.plugin.getCompatibleBranch().satisfyDependencies(
1064						roster,installDirectory,downloadSource);
1065					instcount++;
1066				}
1067			}
1068
1069			if(roster.isEmpty())
1070				return;
1071
1072			boolean cancel = false;
1073			if (updates && roster.getOperationCount() > instcount)
1074				if (GUIUtilities.confirm(window,
1075					"install-plugins.depend",
1076					null,
1077					JOptionPane.OK_CANCEL_OPTION,
1078					JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION)
1079					cancel = true;
1080
1081			if (!cancel)
1082			{
1083				new PluginManagerProgress(window,roster);
1084
1085				roster.performOperationsInAWTThread(window);
1086				pluginModel.update();
1087			}
1088		}
1089
1090		@Override
1091		public void tableChanged(TableModelEvent e)
1092		{
1093			if(pluginModel.isDownloadingList())
1094				return;
1095
1096			if (e.getType() == TableModelEvent.UPDATE)
1097			{
1098				int length = pluginModel.getRowCount();
1099				for (int i = 0; i < length; i++)
1100					if (((Boolean)pluginModel.getValueAt(i,0)).booleanValue())
1101					{
1102						setEnabled(true);
1103						return;
1104					}
1105				setEnabled(false);
1106			}
1107		}
1108	} //}}}
1109
1110	//{{{ EntryCompare class
1111	private static class EntryCompare implements Comparator<Entry>
1112	{
1113		private static final int COLUMN_INSTALL = 0;
1114		private static final int COLUMN_NAME = 1;
1115		private static final int COLUMN_CATEGORY = 2;
1116		private static final int COLUMN_VERSION = 3;
1117		private static final int COLUMN_SIZE = 4;
1118		private static final int COLUMN_RELEASE = 5;
1119
1120		private final int type;
1121
1122		/** 1=up, -1=down */
1123		private final int sortDirection;
1124
1125		EntryCompare(int type, int sortDirection)
1126		{
1127			this.type = type;
1128			this.sortDirection = sortDirection;
1129		}
1130
1131		@Override
1132		public int compare(Entry e1, Entry e2)
1133		{
1134			int result;
1135
1136			switch (type)
1137			{
1138				case COLUMN_INSTALL:
1139					result = (e1.install == e2.install) ? 0 : (e1.install ? 1 : -1);
1140					break;
1141				case COLUMN_NAME:
1142					result = e1.name.compareToIgnoreCase(e2.name);
1143					break;
1144				case COLUMN_CATEGORY:
1145					result = e1.set.compareToIgnoreCase(e2.set);
1146					if (result == 0)
1147					{
1148						result = e1.name.compareToIgnoreCase(e2.name);
1149					}
1150					break;
1151				case COLUMN_VERSION:
1152					// lets avoid NPE. Maybe we should move
1153					// this code to StandardUtilities.compareStrings
1154					if (e1.version == e2.version)
1155					{
1156						result = 0;
1157					}
1158					else if (e1.version == null)
1159					{
1160						result = -1;
1161					}
1162					else if(e2.version == null)
1163					{
1164						result = 1;
1165					}
1166					else
1167					{
1168						result = StandardUtilities.compareStrings(e1.version,
1169											  e2.version,
1170											  true);
1171					}
1172					break;
1173				case COLUMN_SIZE:
1174					result = (e1.size < e2.size)
1175						 ? -1
1176						 : ((e1.size == e2.size)
1177						    ? 0
1178						    : 1);
1179					break;
1180				case COLUMN_RELEASE:
1181					result = (e1.timestamp < e2.timestamp)
1182						 ? -1
1183						 : ((e1.timestamp == e2.timestamp)
1184						    ? 0
1185						    : 1);
1186					break;
1187				default:
1188					result = 0;
1189			}
1190			return result * sortDirection;
1191		}
1192	} //}}}
1193
1194	//{{{ HeaderMouseHandler class
1195	private class HeaderMouseHandler extends MouseAdapter
1196	{
1197		@Override
1198		public void mouseClicked(MouseEvent evt)
1199		{
1200			int column = table.getTableHeader().columnAtPoint(evt.getPoint());
1201			pluginModel.sortDirection *= -1;
1202			pluginModel.sort(column);
1203		}
1204	} //}}}
1205
1206	//{{{ TextRenderer
1207	private static class TextRenderer extends DefaultTableCellRenderer
1208	{
1209		private final DefaultTableCellRenderer tcr;
1210
1211		TextRenderer(DefaultTableCellRenderer tcr)
1212		{
1213			this.tcr = tcr;
1214		}
1215
1216		@Override
1217		public Component getTableCellRendererComponent(JTable table, Object value,
1218			boolean isSelected, boolean hasFocus, int row, int column)
1219		{
1220			if (column == 5)
1221				tcr.setHorizontalAlignment(TRAILING);
1222			else
1223				tcr.setHorizontalAlignment(LEADING);
1224			return tcr.getTableCellRendererComponent(table,value,isSelected,false,row,column);
1225		}
1226	} //}}}
1227
1228	//{{{ KeyboardAction class
1229	private class KeyboardAction extends AbstractAction
1230	{
1231		private KeyboardCommand command = KeyboardCommand.NONE;
1232
1233		KeyboardAction(KeyboardCommand command)
1234		{
1235			this.command = command;
1236		}
1237
1238		@Override
1239		public void actionPerformed(ActionEvent evt)
1240		{
1241			switch (command)
1242			{
1243			case TAB_OUT_FORWARD:
1244				KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
1245				break;
1246			case TAB_OUT_BACK:
1247				KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
1248				break;
1249			case EDIT_PLUGIN:
1250				int[] rows = table.getSelectedRows();
1251				Object[] state = new Object[rows.length];
1252				for (int i=0 ; i<rows.length ; i++)
1253				{
1254					state[i] = pluginModel.getValueAt(rows[i],0);
1255				}
1256				for (int i=0 ; i<rows.length ; i++)
1257				{
1258					pluginModel.setValueAt(state[i].equals(Boolean.FALSE),rows[i],0);
1259				}
1260				break;
1261			case CLOSE_PLUGIN_MANAGER:
1262				window.ok();
1263				break;
1264			default:
1265				throw new InternalError();
1266			}
1267		}
1268	} //}}}
1269
1270	//{{{ TableFocusHandler class
1271	private class TableFocusHandler extends FocusAdapter
1272	{
1273		@Override
1274		public void focusGained(FocusEvent fe)
1275		{
1276			if (-1 == table.getSelectedRow() && table.getRowCount() > 0)
1277			{
1278				table.setRowSelectionInterval(0,0);
1279				JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
1280				scrollbar.setValue(scrollbar.getMinimum());
1281			}
1282			if (-1 == table.getSelectedColumn())
1283			{
1284				table.setColumnSelectionInterval(0,0);
1285			}
1286		}
1287	} //}}}
1288
1289	//{{{ HeaderRenderer
1290	private static class HeaderRenderer extends DefaultTableCellRenderer
1291	{
1292		private final DefaultTableCellRenderer tcr;
1293
1294		HeaderRenderer(DefaultTableCellRenderer tcr)
1295		{
1296			this.tcr = tcr;
1297		}
1298
1299		@Override
1300		public Component getTableCellRendererComponent(JTable table, Object value,
1301							       boolean isSelected, boolean hasFocus,
1302							       int row, int column)
1303		{
1304			JLabel l = (JLabel)tcr.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
1305			PluginTableModel model = (PluginTableModel) table.getModel();
1306			Icon icon = (column == model.sortType)
1307				? (model.sortDirection == 1) ? ASC_ICON : DESC_ICON
1308				: null;
1309			l.setIcon(icon);
1310			// l.setHorizontalTextPosition(l.LEADING);
1311			return l;
1312		}
1313	} //}}}
1314
1315	//}}}
1316
1317	static final Icon ASC_ICON  = GUIUtilities.loadIcon("arrow-asc.png");
1318	static final Icon DESC_ICON = GUIUtilities.loadIcon("arrow-desc.png");
1319}