/****************************************************************************
* Copyright (C) 2013 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.richclient.gui.manage;
import org.openecard.richclient.gui.manage.addon.DefaultSettingsGroup;
import org.openecard.richclient.gui.manage.addon.DefaultSettingsPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Properties;
import javax.annotation.Nonnull;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.openecard.addon.AddonManager;
import org.openecard.addon.AddonRegistry;
import org.openecard.addon.manifest.AddonSpecification;
import org.openecard.addon.manifest.AppExtensionSpecification;
import org.openecard.common.I18n;
import org.openecard.common.util.FileUtils;
import org.openecard.gui.graphics.GraphicsUtil;
import org.openecard.gui.graphics.OecLogoBgWhite;
import org.openecard.richclient.gui.manage.core.ConnectionSettingsAddon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Dialog for the management of add-ons and builtin functionality.
* The dialog hosts a sidebar where one can select the add-on or builtin item to display. The items are
* {@link AddonPanel}s which are configured appropriately.
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class ManagementDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final String LANGUAGE_CODE = System.getProperty("user.language");
private static final Logger logger = LoggerFactory.getLogger(ManagementDialog.class);
private static ManagementDialog runningDialog;
private final I18n lang = I18n.getTranslation("addon");
private final AddonManager manager;
private final AddonRegistry cpReg;
private final AddonRegistry fileReg;
private JPanel selectionPanel;
private JPanel contentPane;
private JList coreList;
private JList addonList;
private JPanel addonPanel;
private JLabel lastImage;
/**
* Creates a new instance of the dialog and displays it.
* This method only permits a single instance, so this is the preferred way to open the dialog.
*
* @param manager
*/
public static synchronized void showDialog(AddonManager manager) {
if (runningDialog == null) {
ManagementDialog dialog = new ManagementDialog(manager);
dialog.addWindowListener(new WindowListener() {
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
ManagementDialog.runningDialog = null;
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
});
dialog.setVisible(true);
runningDialog = dialog;
} else {
// dialog already shown, bring to front
runningDialog.toFront();
}
}
/**
* Create a ManagementDialog instance.
* The preferred way of opening this dialog is the {@link #showDialog()} function which also makes the dialog
* visible and only permits one open instance at a time.
*
* @param manager
*/
public ManagementDialog(AddonManager manager) {
this.manager = manager;
cpReg = manager.getBuiltinRegistry();
fileReg = manager.getExternalRegistry();
Image logo = GraphicsUtil.createImage(OecLogoBgWhite.class, 147, 147);
setIconImage(logo);
setTitle(lang.translationForKey("addon.title"));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setMinimumSize(new Dimension(640, 420));
setSize(730, 480);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
addonPanel = new JPanel(new BorderLayout(), true);
contentPane.add(addonPanel, BorderLayout.CENTER);
JPanel selectionWrapper = new JPanel(new BorderLayout());
contentPane.add(selectionWrapper, BorderLayout.WEST);
selectionPanel = new JPanel();
selectionWrapper.add(selectionPanel, BorderLayout.NORTH);
selectionWrapper.add(Box.createHorizontalGlue(), BorderLayout.CENTER);
GridBagLayout selectionLayout = new GridBagLayout();
selectionLayout.rowHeights = new int[]{0, 0, 0, 0};
selectionLayout.columnWeights = new double[]{1.0};
selectionLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 1.0};
selectionPanel.setLayout(selectionLayout);
createCoreList();
createAddonList();
setupCoreList();
setupAddonList();
setLocationRelativeTo(null);
}
/**
* Sets the logo in the main panel describing the current displayed add-on page.
* This method should be called when the add-on page is replaced with another one.
*
* @param logo Image of the logo to display. Must be scaled to size 45x45.
*/
public void setLogo(@Nonnull Image logo) {
if (lastImage != null) {
selectionPanel.remove(lastImage);
}
lastImage = new JLabel(new ImageIcon(logo));
GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.insets = new Insets(5, 0, 6, 10);
labelConstraints.anchor = GridBagConstraints.NORTH;
labelConstraints.gridx = 0;
labelConstraints.gridy = 0;
selectionPanel.add(lastImage, labelConstraints);
selectionPanel.revalidate();
selectionPanel.repaint();
}
private void createCoreList() {
JLabel label = new JLabel(lang.translationForKey("addon.list.core"));
label.setFont(label.getFont().deriveFont(Font.BOLD));
GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.insets = new Insets(5, 0, 5, 10);
labelConstraints.anchor = GridBagConstraints.NORTH;
labelConstraints.gridx = 0;
labelConstraints.gridy = 1;
selectionPanel.add(label, labelConstraints);
coreList = new JList();
coreList.setFont(coreList.getFont().deriveFont(Font.PLAIN));
coreList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
GridBagConstraints coreListConstraints = new GridBagConstraints();
coreListConstraints.fill = GridBagConstraints.HORIZONTAL;
coreListConstraints.insets = new Insets(0, 5, 5, 10);
coreListConstraints.anchor = GridBagConstraints.NORTH;
coreListConstraints.gridx = 0;
coreListConstraints.gridy = 2;
AddonSelectionModel model = new AddonSelectionModel(this, addonPanel);
coreList.setModel(model);
coreList.addListSelectionListener(model);
addWindowListener(model); // save current addon settings when closed
// add addon panels
model.addElement(lang.translationForKey("addon.list.core.connection"), new ConnectionSettingsAddon());
// this assumes that all addons in the ClasspathRegistry are core addons
// an ActionPanel for every addon that has one ore more AppExtensionActions will be added
for (AddonSpecification desc : cpReg.listAddons()) {
ArrayList<AppExtensionSpecification> applicationActions = desc.getApplicationActions();
if (applicationActions.size() > 0) {
String description = desc.getLocalizedDescription(LANGUAGE_CODE);
String name = desc.getLocalizedName(LANGUAGE_CODE);
Image logo = loadLogo(desc.getLogo());
JPanel actionPanel = createActionPanel(desc);
AddonPanel nextPanel = new AddonPanel(actionPanel, name, description, logo);
model.addElement(name, nextPanel);
}
}
selectionPanel.add(coreList, coreListConstraints);
}
/**
* Creates an ActionPanel that has an ActionEntryPanel for every AppExtensionAction of the given addon.
*
* @param desc AddonSpecification for the addon
* @return the created ActionPanel
*/
private ActionPanel createActionPanel(AddonSpecification desc) {
ActionPanel actionPanel = new ActionPanel();
for (AppExtensionSpecification action : desc.getApplicationActions()) {
ActionEntryPanel actionEntryPanel = new ActionEntryPanel(desc, action, manager);
actionPanel.addActionEntry(actionEntryPanel);
}
return actionPanel;
}
/**
* Load the logo from the given path as {@link Image}.
*
* @param logoPath path to the logo
* @return the logo-{@link Image} if loading was successful, otherwise {@code null}
*/
private static Image loadLogo(String logoPath) {
if (logoPath == null || logoPath.isEmpty()) {
return null;
}
try {
InputStream in = FileUtils.resolveResourceAsStream(ManagementDialog.class, logoPath);
ImageIcon icon = new ImageIcon(FileUtils.toByteArray(in));
if (icon.getIconHeight() < 0 || icon.getIconWidth() < 0) {
// supplied data was no image, btw the image API sucks
return null;
}
return icon.getImage();
} catch (IOException ex) {
// ignore and let the default decide
return null;
}
}
private void createAddonList() {
JLabel label = new JLabel(lang.translationForKey("addon.list.addon"));
label.setFont(label.getFont().deriveFont(Font.BOLD));
GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.insets = new Insets(5, 0, 5, 10);
labelConstraints.anchor = GridBagConstraints.NORTH;
labelConstraints.gridx = 0;
labelConstraints.gridy = 3;
selectionPanel.add(label, labelConstraints);
// TODO: remove this code
//label.setVisible(false);
addonList = new JList();
addonList.setFont(addonList.getFont().deriveFont(Font.PLAIN));
addonList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
GridBagConstraints addonListConstraints = new GridBagConstraints();
addonListConstraints.fill = GridBagConstraints.HORIZONTAL;
addonListConstraints.insets = new Insets(0, 5, 5, 10);
addonListConstraints.anchor = GridBagConstraints.NORTH;
addonListConstraints.gridx = 0;
addonListConstraints.gridy = 4;
AddonSelectionModel model = new AddonSelectionModel(this, addonPanel);
addonList.setModel(model);
addonList.addListSelectionListener(model);
addWindowListener(model); // save current addon settings when closed
// add addon panels
// this assumes that all addons in the FileRegistry are non core addons
for (AddonSpecification desc : fileReg.listAddons()) {
String description = desc.getLocalizedDescription(LANGUAGE_CODE);
String name = desc.getLocalizedName(LANGUAGE_CODE);
Image logo = loadLogo(desc.getLogo());
Properties properties = new Properties();
try {
File config = new File(FileUtils.getHomeConfigDir().getAbsolutePath() + File.separatorChar + "plugins"
+ File.separatorChar + desc.getId() + ".properties");
if (config.exists()) {
properties.load(new FileReader(config));
} else {
logger.debug("A properties file for the addon with id {} does not yet exist", desc.getId());
}
} catch (SecurityException e) {
logger.error("Failed to load properties file for addon with id " + desc.getId(), e);
} catch (IOException e) {
logger.error("Failed to load properties file for addon with id " + desc.getId(), e);
}
// TODO: what title should we set?
SettingsGroup settingsGroup = new DefaultSettingsGroup("", properties, desc);
AddonPanel nextPanel = new AddonPanel(new DefaultSettingsPanel(settingsGroup), name, description, logo);
model.addElement(name, nextPanel);
}
selectionPanel.add(addonList, addonListConstraints);
}
private void setupCoreList() {
coreList.addListSelectionListener(new ClearSelectionListener(addonList));
coreList.setSelectedIndex(0);
}
private void setupAddonList() {
addonList.addListSelectionListener(new ClearSelectionListener(coreList));
}
private class ClearSelectionListener implements ListSelectionListener {
private final JList otherList;
public ClearSelectionListener(JList otherList) {
this.otherList = otherList;
}
@Override
public void valueChanged(ListSelectionEvent e) {
Object source = e.getSource();
if (source instanceof JComponent) {
JComponent component = (JComponent) source;
// only do this when we have the focus
if (! e.getValueIsAdjusting() && component.hasFocus()) {
otherList.clearSelection();
}
}
}
}
}