/*
* Copyright (C) 2011 Andrea Schweer
*
* This file is part of the Digital Parrot.
*
* The Digital Parrot is free software; you can redistribute it and/or modify
* it under the terms of the Eclipse Public License as published by the Eclipse
* Foundation or its Agreement Steward, either version 1.0 of the License, or
* (at your option) any later version.
*
* The Digital Parrot 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 Eclipse Public License for
* more details.
*
* You should have received a copy of the Eclipse Public License along with the
* Digital Parrot. If not, see http://www.eclipse.org/legal/epl-v10.html.
*
*/
package net.schweerelos.parrot;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.schweerelos.parrot.model.NodeWrapper;
import net.schweerelos.parrot.model.ParrotModel;
import net.schweerelos.parrot.model.ParrotModelFactory;
import net.schweerelos.parrot.model.ParrotModelFactory.Style;
import net.schweerelos.parrot.ui.MainViewComponent;
import net.schweerelos.parrot.ui.NavigatorComponent;
import net.schweerelos.parrot.ui.ParrotStateListener;
import net.schweerelos.parrot.ui.PickListener;
import net.schweerelos.parrot.ui.UnknownStyleException;
import net.schweerelos.parrot.ui.UserInterfaceManager;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
public class CombinedParrotApp extends JFrame {
private static final long serialVersionUID = 1L;
private static final String APP_TITLE = "The Digital Parrot";
private ParrotModel model;
private List<MainViewComponent> mainViews = new ArrayList<MainViewComponent>(2);
private MainViewComponent listView;
private MainViewComponent graphView;
private MainViewComponent activeMainView;
private List<NavigatorComponent> navigators;
private Map<Window, Point> preferredFrameLocations = new HashMap<Window, Point>();
public CombinedParrotApp(Properties properties) {
super();
setModalExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
model = ParrotModelFactory.getInstance(Style.COMBINED).createModel();
initGUI(properties);
}
private void initGUI(Properties properties) {
try {
this.setTitle(APP_TITLE);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
for (MainViewComponent mainViewComponent : mainViews) {
if (mainViewComponent instanceof ParrotStateListener) {
((ParrotStateListener) mainViewComponent).parrotExiting();
}
}
}
});
setSize(920, 690);
getContentPane().setLayout(new BorderLayout());
UserInterfaceManager uiManager = new UserInterfaceManager(properties);
navigators = new ArrayList<NavigatorComponent>(4);
// main view
listView = uiManager.createMainViewComponent(Style.TABLE);
graphView = uiManager.createMainViewComponent(Style.GRAPH);
mainViews.add(listView);
mainViews.add(graphView);
final JTabbedPane mainPanel = new JTabbedPane();
mainPanel.add(graphView.getTitle(), graphView.asJComponent());
mainPanel.add(listView.getTitle(), listView.asJComponent());
mainPanel.setSelectedIndex(0);
activeMainView = graphView;
mainPanel.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (e.getSource() != mainPanel) {
return;
}
int selectedIndex = mainPanel.getSelectedIndex();
if (selectedIndex == 0) {
activeMainView = graphView;
} else if (selectedIndex == 1) {
activeMainView = listView;
} else {
Logger logger = Logger.getLogger(CombinedParrotApp.class);
logger.warn("unknown tab index selected: " + selectedIndex);
}
}
});
add(mainPanel, BorderLayout.CENTER);
// navigators
JToolBar navigatorsBar = new JToolBar(JToolBar.HORIZONTAL);
navigatorsBar.setMargin(new Insets(0, 11, 0, 0));
navigatorsBar.setFloatable(false);
getContentPane().add(navigatorsBar, BorderLayout.PAGE_START);
// timeline
NavigatorComponent timelineNavigator = uiManager.createTimelineNavigationComponent();
navigators.add(timelineNavigator);
JFrame timelineFrame = new JFrame(timelineNavigator.getNavigatorName() + " – " + APP_TITLE);
timelineFrame.getContentPane().add(timelineNavigator.asJComponent());
timelineFrame.pack();
Point preferredLocation = new Point(0,0);
preferredFrameLocations.put(timelineFrame, preferredLocation);
if (timelineNavigator.hasShowHideListener()) {
timelineFrame.addComponentListener(timelineNavigator.getShowHideListener());
}
timelineFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JToggleButton timelineButton
= setupNavigatorButton(timelineNavigator.getNavigatorName(),
timelineNavigator.getAcceleratorKey(),
timelineNavigator);
navigatorsBar.add(timelineButton);
// map
NavigatorComponent mapNavigator = uiManager.createMapNavigationComponent();
navigators.add(mapNavigator);
JFrame mapFrame = new JFrame(mapNavigator.getNavigatorName() + " – " + APP_TITLE);
mapFrame.getContentPane().add(mapNavigator.asJComponent());
mapFrame.pack();
preferredLocation = new Point(0, Toolkit.getDefaultToolkit().getScreenSize().height - mapFrame.getHeight());
preferredFrameLocations.put(mapFrame, preferredLocation);
if (mapNavigator.hasShowHideListener()) {
mapFrame.addComponentListener(mapNavigator.getShowHideListener());
}
mapFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JToggleButton mapButton
= setupNavigatorButton(mapNavigator.getNavigatorName(),
mapNavigator.getAcceleratorKey(),
mapNavigator);
navigatorsBar.add(mapButton);
// search
NavigatorComponent searchNavigator = uiManager.createSearchComponent();
navigators.add(searchNavigator);
JFrame searchFrame = new JFrame(searchNavigator.getNavigatorName() + " – " + APP_TITLE);
searchFrame.getContentPane().add(searchNavigator.asJComponent());
searchFrame.pack();
preferredLocation = new Point(Toolkit.getDefaultToolkit().getScreenSize().width - searchFrame.getWidth(), 0);
preferredFrameLocations.put(searchFrame, preferredLocation);
if (searchNavigator.hasShowHideListener()) {
searchFrame.addComponentListener(searchNavigator.getShowHideListener());
}
searchFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JToggleButton searchButton
= setupNavigatorButton(searchNavigator.getNavigatorName(),
searchNavigator.getAcceleratorKey(),
searchNavigator);
navigatorsBar.add(searchButton);
// connections
NavigatorComponent chainNavigator = uiManager.createChainNavigationComponent();
navigators.add(chainNavigator);
if (chainNavigator instanceof PickListener) {
for (MainViewComponent mainViewComponent : mainViews) {
mainViewComponent.addPickListener((PickListener) chainNavigator);
}
}
if (chainNavigator.hasShowHideListener()) {
chainNavigator.asJComponent().addComponentListener(chainNavigator.getShowHideListener());
}
JToggleButton connectionsButton
= setupNavigatorButton(chainNavigator.getNavigatorName(),
chainNavigator.getAcceleratorKey(),
chainNavigator);
navigatorsBar.add(connectionsButton);
add(chainNavigator.asJComponent(), BorderLayout.PAGE_END);
chainNavigator.asJComponent().setVisible(false);
} catch (RuntimeException e) {
e.printStackTrace(System.err);
System.exit(1);
} catch (UnknownStyleException e) {
e.printStackTrace(System.err);
System.exit(1);
}
}
public Map<String, List<Action>> getNavigatorActionsForNode(NodeWrapper node) {
Map<String, List<Action>> map = new HashMap<String, List<Action>>();
for (NavigatorComponent navigator : navigators) {
map.put(navigator.getNavigatorName(), navigator.getActionsForNode(node));
}
return map;
}
public Map<String, List<Action>> getNavigatorActionsForType(NodeWrapper type) {
Map<String, List<Action>> map = new HashMap<String, List<Action>>();
for (NavigatorComponent navigator : navigators) {
map.put(navigator.getNavigatorName(), navigator.getActionsForType(type));
}
return map;
}
/**
* @param args
*/
@SuppressWarnings("static-access")
public static void main(String[] args) {
CommandLineParser parser = new PosixParser();
// create the Options
Options options = new Options();
options.addOption(OptionBuilder.withLongOpt("help")
.withDescription("Shows usage information and quits the program")
.create("h"));
options.addOption(OptionBuilder.withLongOpt("datafile")
.withDescription("The file with instances to use (required)")
.hasArg()
.withArgName("file")
.isRequired()
.create("f"));
options.addOption(OptionBuilder.withLongOpt("properties")
.withDescription("Properties file to use. Default: " + System.getProperty("user.home") + File.separator + ".digital-parrot" + File.separator + "parrot.properties")
.hasArg()
.withArgName("prop")
.create("p"));
try {
// parse the command line arguments
CommandLine line = parser.parse(options, args);
if (line.hasOption("h")) {
// this is only executed when all required options are present _and_ the help option is specified!
printHelp(options);
return;
}
String datafile = line.getOptionValue("f");
if (!datafile.startsWith("file:") || !datafile.startsWith("http:")) {
datafile = "file:" + datafile;
}
String propertiesPath = System.getProperty("user.home") + File.separator + ".digital-parrot" + File.separator + "parrot.properties";
if (line.hasOption("p")) {
propertiesPath = line.getOptionValue("p");
}
final Properties properties = new Properties();
File propertiesFile = new File(propertiesPath);
if (propertiesFile.exists() && propertiesFile.canRead()) {
try {
properties.load(new FileReader(propertiesFile));
} catch (FileNotFoundException e) {
e.printStackTrace(System.err);
System.exit(1);
} catch (IOException e) {
e.printStackTrace(System.err);
System.exit(1);
}
}
final String url = datafile; // we need a "final" var for the anonymous class
SwingUtilities.invokeLater(new Runnable() {
public void run() {
CombinedParrotApp inst = null;
try {
inst = new CombinedParrotApp(properties);
inst.loadModel(url);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"There has been an error while starting the program.\nThe program will exit now.",
APP_TITLE + " – Error",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace(System.err);
System.exit(1);
}
if (inst != null) {
inst.setLocationRelativeTo(null);
inst.setVisible(true);
}
}
});
} catch (ParseException exp) {
printHelp(options);
}
}
private void loadModel(String url) {
model.loadData(url);
graphView.setModel(model.asGraphModel());
listView.setModel(model.asListModel());
for (NavigatorComponent navigator : navigators) {
if (navigator != null) {
navigator.setModel(model);
}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
validate();
}
});
}
@SuppressWarnings("serial")
private JToggleButton setupNavigatorButton(final String name, final String accelerator, final NavigatorComponent navigator) {
final Component component = navigator.asJComponent();
AbstractAction showNavigatorAction = new AbstractAction(name) {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JToggleButton)) {
return;
}
final Window window;
if (component instanceof Window) {
window = (Window) component;
} else {
window = SwingUtilities.getWindowAncestor(component);
}
JToggleButton button = (JToggleButton) e.getSource();
boolean show = button.isSelected();
if (show) {
if (window != CombinedParrotApp.this && preferredFrameLocations.containsKey(window)) {
window.setLocation(preferredFrameLocations.get(window));
}
}
if (navigator.tellSelectionWhenShown()) {
Collection<NodeWrapper> selectedNodes = activeMainView.getSelectedNodes();
navigator.setSelectedNodes(selectedNodes);
}
component.setVisible(show);
if (show) {
window.setVisible(true);
} else if (window != CombinedParrotApp.this) {
window.setVisible(false);
}
}
};
showNavigatorAction.putValue(Action.ACCELERATOR_KEY,
KeyStroke.getKeyStroke("control " + accelerator));
final JToggleButton button = new JToggleButton(showNavigatorAction);
button.setToolTipText("Show " + name.toLowerCase());
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
button.setToolTipText((button.isSelected() ? "Hide " : "Show ") + name.toLowerCase());
}
});
final Window window;
if (component instanceof Window) {
window = (Window) component;
} else {
window = SwingUtilities.getWindowAncestor(component);
}
if (window != null) {
window.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
button.setSelected(false);
if (window != CombinedParrotApp.this) {
preferredFrameLocations.put(window, window.getLocation());
}
}
@Override
public void componentShown(ComponentEvent e) {
button.setSelected(true);
}
});
}
return button;
}
private static void printHelp(Options options) {
HelpFormatter helpFormat = new HelpFormatter();
helpFormat.printHelp(APP_TITLE, options, true);
}
}