PluginManagementDialog.java

/**
 * VStar: a statistical analysis tool for variable star data.
 * Copyright (C) 2010  AAVSO (http://www.aavso.org/)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */
package org.aavso.tools.vstar.ui.dialog.plugin.manager;

import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.aavso.tools.vstar.ui.dialog.MessageBox;
import org.aavso.tools.vstar.ui.mediator.DocumentManager;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.resources.PluginLoader;
import org.aavso.tools.vstar.util.help.Help;

/**
 * The plugin management dialog.
 */
@SuppressWarnings("serial")
public class PluginManagementDialog extends JDialog implements ListSelectionListener {

	private PluginManager manager;

	private JList pluginList;
	private DefaultListModel pluginListModel;

	private JButton dismissButton;
	private JButton installButton;
	private JButton updateButton;
	private JButton deleteButton;
	private JCheckBox allCheckBox;
	private JButton helpButton;
	private JButton closeProgramButton;

	private final String DIALOG_TITLE = "Plug-in Manager";
	private final String DIALOG_TITLE_MOD = "Plug-in Manager: PROGRAM RESTART NEEDED";
	private boolean restartNeeded = false;

	private static boolean pluginManagementActive = false;

	/**
	 * Constructor
	 */
	public PluginManagementDialog(PluginManager manager) {
		super(DocumentManager.findActiveWindow());

		// System.out.println(Thread.currentThread().getId());

		// TODO: localise
		setTitle(DIALOG_TITLE);

		this.manager = manager;

		JPanel topPane = new JPanel();
		topPane.setLayout(new BoxLayout(topPane, BoxLayout.PAGE_AXIS));
		topPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

		topPane.add(createURLpane(PluginManager.getPluginsBaseUrl()));
		topPane.add(createListPane());
		topPane.add(createButtonPane());
		topPane.add(createButtonPane2());

		getContentPane().add(topPane);

		this.getRootPane().setDefaultButton(dismissButton);

		this.pack();

		// Set the initial button states.
		if (!pluginListModel.isEmpty()) {
			pluginList.setSelectedIndex(0);
			int index = pluginList.getSelectedIndex();
			if (index != -1) {
				String desc = (String) pluginListModel.get(index);
				setButtonStates(desc);
			}
		}

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) {
				setPluginManagementActive(false);
			}
		});
		this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		// this.setModal(true);

		this.setLocationRelativeTo(Mediator.getUI().getContentPane());
		this.setVisible(true);
	}

	public static synchronized void setPluginManagementActive(boolean b) {
		// System.out.println("setPluginManagementActive: " +
		// Thread.currentThread().getId());
		pluginManagementActive = b;
	}

	public static synchronized boolean getPluginManagementActive() {
		// System.out.println("getPluginManagementActive: " +
		// Thread.currentThread().getId());
		return pluginManagementActive;
	}

	private void closePluginLoaders() {
		PluginLoader.closePluginLoaders();
	}

	private void setRestartFlag() {
		restartNeeded = true;
		setTitle(DIALOG_TITLE_MOD);
		closeProgramButton.setVisible(true);
		pack();
	}

	private JPanel createURLpane(String pluginBaseURL) {
		JPanel panel = new JPanel();
		JLabel label = new JLabel(pluginBaseURL);
		panel.add(label);
		return panel;
	}
	
	private JPanel createListPane() {
		JPanel panel = new JPanel();
		panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

		// Get a combined, unique list of plugin descriptions for adding to the
		// model.
		Set<String> pluginDescSet = new LinkedHashSet<String>();
		pluginDescSet.addAll(manager.getLocalDescriptions());
		pluginDescSet.addAll(manager.getRemoteDescriptions());

		pluginListModel = new DefaultListModel();

		for (String desc : pluginDescSet) {
			pluginListModel.addElement(desc);
		}

		pluginList = new JList(pluginListModel);
		// PluginManager can install/delete one plugin at once so multiply selection is
		// useless and may confuse
		pluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION/* .SINGLE_INTERVAL_SELECTION */);
		pluginList.addListSelectionListener(this);
		pluginList.setCellRenderer(new pluginManagerCellRenderer());
		JScrollPane modelListScroller = new JScrollPane(pluginList);

		panel.add(modelListScroller);

		return panel;
	}

	private JPanel createButtonPane() {
		JPanel panel = new JPanel(new FlowLayout());

		helpButton = new JButton("Help");
		helpButton.addActionListener(createHelpButtonListener());
		panel.add(helpButton);
		
		dismissButton = new JButton("Dismiss");
		dismissButton.addActionListener(createDismissButtonListener());
		dismissButton.setEnabled(true);
		panel.add(dismissButton);

		installButton = new JButton("Install");
		installButton.addActionListener(createInstallButtonListener());
		installButton.setEnabled(false);
		panel.add(installButton);

		updateButton = new JButton("Update");
		updateButton.addActionListener(createUpdateButtonListener());
		updateButton.setEnabled(false);
		panel.add(updateButton);

		deleteButton = new JButton("Delete");
		deleteButton.addActionListener(createDeleteButtonListener());
		deleteButton.setEnabled(false);
		panel.add(deleteButton);

		allCheckBox = new JCheckBox("All?");
		allCheckBox.addActionListener((e) -> {
			enableAllButtons();			
			if (allCheckBox.isSelected()) {
				pluginList.setEnabled(false);
				setButtonStatesForBatchOp();
			} else {
				pluginList.setEnabled(true);
				setButtonStatesForSelectedPlugin();
			}
		});
		panel.add(allCheckBox);

		this.getRootPane().setDefaultButton(dismissButton);

		return panel;
	}

	private JPanel createButtonPane2() {
		JPanel panel = new JPanel(new FlowLayout());

		closeProgramButton = new JButton("VStar should be restarted. Click to close the program.");
		closeProgramButton.addActionListener(createCloseProgramButtonListener());
		closeProgramButton.setEnabled(true);
		closeProgramButton.setVisible(false);
		closeProgramButton.setBackground(Color.YELLOW);
		closeProgramButton.setForeground(Color.RED);
		panel.add(closeProgramButton);

		return panel;
	}

	private void enableAllButtons() {
		dismissButton.setEnabled(true);
		deleteButton.setEnabled(true);
		installButton.setEnabled(true);
		updateButton.setEnabled(true);
		allCheckBox.setEnabled(true);
		closeProgramButton.setEnabled(true);
		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
	}
	
	private void disableAllButtons() {
		dismissButton.setEnabled(false);
		deleteButton.setEnabled(false);
		installButton.setEnabled(false);
		updateButton.setEnabled(false);
		allCheckBox.setEnabled(false);
		closeProgramButton.setEnabled(false);
		setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
	}

	private void setButtonStatesForBatchOp() {
		deleteButton.setEnabled(true);
		installButton.setEnabled(true);
		updateButton.setEnabled(false);
	}
	
	// Set button states for currently selected plugin
	private void setButtonStatesForSelectedPlugin() {
		int index = pluginList.getSelectedIndex();
		if (index != -1) {
			String desc = (String) pluginListModel.get(index);
			setButtonStates(desc);
		}
	}
	
	// Set the button states based upon the specified description.
	private void setButtonStates(String desc) {
		// Start with a blank slate.
		installButton.setEnabled(false);
		updateButton.setEnabled(false);
		deleteButton.setEnabled(false);
		// deleteAllButton.setEnabled(false);

		if (manager.isLocal(desc) && !manager.isRemote(desc)) {
			deleteButton.setEnabled(true);
			// deleteAllButton.setEnabled(true);
		} else if (!manager.isLocal(desc) && manager.isRemote(desc)) {
			installButton.setEnabled(true);
		} else if (manager.isLocal(desc) && manager.isRemote(desc)) {
			if (manager.arePluginsEqual(desc)) {
				deleteButton.setEnabled(true);
				// deleteAllButton.setEnabled(true);
			} else {
				updateButton.setEnabled(true);
				deleteButton.setEnabled(true);
				// deleteAllButton.setEnabled(true);
			}
		}
	}

	// List selection listener to update button states.
	public void valueChanged(ListSelectionEvent e) {
		if (e.getValueIsAdjusting() == false) {
			int index = pluginList.getSelectedIndex();
			if (index != -1) {
				String desc = (String) pluginListModel.get(index);
				setButtonStates(desc);
			}
		}
	}

	// Return a listener for the "Dismiss" button.
	private ActionListener createDismissButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				setPluginManagementActive(false);
				setVisible(false);
				dispose();
			}
		};
	}
	
  	private void updateInterfaceAfterPluginOp() {
		enableAllButtons();			
		if (allCheckBox.isSelected()) {
			setButtonStatesForBatchOp();
		} else {
			setButtonStatesForSelectedPlugin();
		}
  		setRestartFlag();
  		pluginList.repaint();
  	}
	

	// Return a listener for the "Install" button.
	private ActionListener createInstallButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (allCheckBox.isSelected()) {
					if (!MessageBox.showConfirmDialog("Plug-in Manager", "Install all plug-ins?"))
						return;
				}
				int index = pluginList.getSelectedIndex();
				final Set<String> descs = new HashSet<String>();
				if (!allCheckBox.isSelected()) {
					descs.add((String) pluginListModel.get(index));
				} else {
					descs.addAll(manager.getRemoteDescriptions());
				}

				PluginManagementOperation op = new PluginManagementOperation(manager,
						"Performing Plug-in Install Operation") {
					@Override
					public void execute() {
						try {
							for (String desc : descs) {
								manager.installPlugin(desc, PluginManager.Operation.INSTALL);
							}
						} finally {
							javax.swing.SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									updateInterfaceAfterPluginOp();				
								}
							});
						}
					}
				};
				// in "Install All" mode all plugins, including existing, will be installed
				closePluginLoaders(); // under Windows, JAR file handles must be closed to allow overwriting
				disableAllButtons();
				Mediator.getInstance().performPluginManagerOperation(op);
			}
		};
	}

	// Return a listener for the "Update" button.
	private ActionListener createUpdateButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (allCheckBox.isSelected()) {
					if (!MessageBox.showConfirmDialog("Plug-in Manager", "Update all plug-ins?"))
						return;
				}
				int index = pluginList.getSelectedIndex();
				final Set<String> descs = new HashSet<String>();
				if (!allCheckBox.isSelected()) {
					descs.add((String) pluginListModel.get(index));
				} else {
					descs.addAll(manager.getRemoteDescriptions());
				}
 
				PluginManagementOperation op = new PluginManagementOperation(manager,
						"Performing Plug-in Update Operation") {
					@Override
					public void execute() {
						try {
							for (String desc : descs) {
								manager.installPlugin(desc, PluginManager.Operation.UPDATE);
							}
						} finally {
							javax.swing.SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									updateInterfaceAfterPluginOp();				
								}
							});
						}
					}
				};
				closePluginLoaders(); // under Windows, JAR file handles must be closed to allow overwriting				
				disableAllButtons();
				Mediator.getInstance().performPluginManagerOperation(op);
			}
		};
	}

	// Return a listener for the "Delete" button.
	private ActionListener createDeleteButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (allCheckBox.isSelected()) {
					if (!MessageBox.showConfirmDialog("Plug-in Manager", "Delete all plug-ins?"))
						return;
				}
				int index = pluginList.getSelectedIndex();
				final Set<String> descs = new HashSet<String>();
				if (!allCheckBox.isSelected()) {
					descs.add((String) pluginListModel.get(index));
				} else {
					descs.addAll(manager.getLocalDescriptions());
				}

				PluginManagementOperation op = new PluginManagementOperation(manager,
						"Performing Plug-in Delete Operation") {
					@Override
					public void execute() {
						// Do not use manager.deleteAllPlugins() here,
						// it displays dialogs yet would be called from background thread here.
						try {
							for (String desc : descs) {
								manager.deletePlugin(desc);
							}
						} finally {
							javax.swing.SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									// If not also remote, remove from list.
									for (String desc : descs) {
										if (!manager.isRemote(desc)) {
											pluginListModel.remove(pluginListModel.indexOf(desc));
										}
									}
									updateInterfaceAfterPluginOp();				
								}
							});
						}
					}
				};
				closePluginLoaders(); // under Windows, JAR file handles must be closed				
				disableAllButtons();
				Mediator.getInstance().performPluginManagerOperation(op);
			}
		};
	}

	// Return a listener for the "Help" button.
	private ActionListener createHelpButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String plugin_doc_name = null;
				if (!allCheckBox.isSelected()) {
					int index = pluginList.getSelectedIndex();
					String description = (String)(pluginListModel.get(index));
					plugin_doc_name = manager.getPluginDocName(description);
				}
				Help.openPluginHelp(plugin_doc_name);
			}
		};
	}

	// Return a listener for the "Close Program" button.
	private ActionListener createCloseProgramButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				Mediator mediator = Mediator.getInstance();
				mediator.quit();
			}
		};
	}

	
	private class pluginManagerCellRenderer extends DefaultListCellRenderer  {

		@Override
		public Component getListCellRendererComponent(JList<?> list,
			Object value, int index, boolean isSelected, boolean cellHasFocus) {
			JLabel l = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
			String desc = (String) pluginListModel.get(index);
			Font f = l.getFont();
			if (manager.isLocal(desc)) {
				l.setFont(f.deriveFont(f.getStyle() | Font.BOLD));
				if (!manager.arePluginsEqual(desc)) {
					l.setForeground(Color.GRAY);
				}
			}
			return l;
		}
	}
}