// This file is part of PleoCommand:
// Interactively control Pleo with psychobiological parameters
//
// Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Boston, USA.
package pleocmd.itfc.gui;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import pleocmd.Log;
import pleocmd.StandardInput;
import pleocmd.cfg.ConfigBounds;
import pleocmd.cfg.ConfigInt;
import pleocmd.cfg.Configuration;
import pleocmd.cfg.ConfigurationInterface;
import pleocmd.cfg.Group;
import pleocmd.exc.ConfigurationException;
import pleocmd.exc.StateException;
import pleocmd.itfc.gui.Layouter.Button;
import pleocmd.pipe.Pipe;
import pleocmd.pipe.PipePartDetection;
/**
* @author oliver
*/
public final class MainFrame extends JFrame implements ConfigurationInterface {
private static final long serialVersionUID = 7174844214646208915L;
private static final Timer AUTOSAVE_TIMER = new Timer(
"Configuration AutoSave Timer", true);
private static MainFrame guiFrame;
private static boolean hasGUI;
private final ConfigBounds cfgBounds = new ConfigBounds("Bounds");
private final ConfigInt cfgSplitterPos = new ConfigInt("Splitter Position",
-1);
private final MainPipePanel mainPipePanel;
private final MainLogPanel mainLogPanel;
private final MainInputPanel mainInputPanel;
private final JSplitPane splitPane;
private final JButton btnHelp;
private final JLabel lblStatus;
private final JButton btnExit;
private final List<AutoDisposableWindow> knownWindows;
private final Pipe pipe;
private Thread pipeThread;
private final Map<Object, String> statusMessages;
private MainFrame() {
// don't change the order of the following lines !!!
// we need this order to avoid race conditions
knownWindows = new ArrayList<AutoDisposableWindow>();
statusMessages = new HashMap<Object, String>();
guiFrame = this;
mainLogPanel = new MainLogPanel();
pipe = new Pipe(Configuration.getMain());
hasGUI = true;
Log.setMinLogType(Log.getMinLogType());
Log.setGUIStatusKnown();
mainInputPanel = new MainInputPanel();
mainPipePanel = new MainPipePanel();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ToolTipManager.sharedInstance().setInitialDelay(500);
ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
ToolTipManager.sharedInstance().setReshowDelay(0);
Log.detail("Creating GUI-Frame");
setTitle("PleoCommand");
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent e) {
exit();
}
});
// Add components
final Layouter lay = new Layouter(this);
lay.addWholeLine(mainPipePanel, false);
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true,
mainLogPanel, mainInputPanel);
splitPane.setResizeWeight(0.75);
lay.addWholeLine(splitPane, true);
btnHelp = lay.addButton(Button.Help,
Layouter.help(this, getClass().getSimpleName()));
lblStatus = new JLabel("...", SwingConstants.CENTER);
lay.add(lblStatus, true);
btnExit = lay.addButton("Exit", "application-exit",
"Cancel running pipe (if any) and exit the application",
new Runnable() {
@Override
public void run() {
exit();
}
});
pack();
setLocationRelativeTo(null);
try {
Configuration.getMain().registerConfigurableObject(this,
getClass().getSimpleName());
} catch (final ConfigurationException e) {
Log.error(e);
}
Log.detail("GUI-Frame created");
PipePartDetection.checkStaticValidity();
AUTOSAVE_TIMER.schedule(new TimerTask() {
@Override
public void run() {
Log.detail("Auto-Saving configuration");
try {
Configuration.getMain().writeToDefaultFile();
} catch (final ConfigurationException e) {
Log.error(e);
}
}
}, 10000, 10000);
}
public void showModalGUI() {
Log.info("Application started");
updateState();
HelpDialog.closeHelpIfOpen();
setVisible(true);
}
public static MainFrame the() {
if (guiFrame == null) new MainFrame();
return guiFrame;
}
public static boolean hasGUI() {
return hasGUI;
}
public MainPipePanel getMainPipePanel() {
return mainPipePanel;
}
public MainLogPanel getMainLogPanel() {
return mainLogPanel;
}
public MainInputPanel getMainInputPanel() {
return mainInputPanel;
}
public void addLog(final Log log) {
mainLogPanel.addLog(log);
}
protected void exit() {
if (isPipeRunning()) {
if (JOptionPane.showOptionDialog(this,
"The pipe is still running. Exiting "
+ "will abort the pipe.", "Error",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, null, null) != JOptionPane.YES_OPTION) return;
abortPipeThread();
}
try {
Configuration.getMain().unregisterConfigurableObject(this);
Configuration.getMain().writeToDefaultFile();
} catch (final ConfigurationException e) {
Log.error(e);
}
dispose();
// dispose all other dialogs and frames, so that the
// Java AWT thread can exit cleanly
// special case: ErrorDialog will still be shown if it has unread
// messages (need copy because of concurrent modifications)
final List<AutoDisposableWindow> copy = new ArrayList<AutoDisposableWindow>(
knownWindows);
for (final AutoDisposableWindow wnd : copy)
wnd.autoDispose();
ErrorDialog.canDisposeIfHidden();
HelpDialog.closeHelpIfOpen();
Log.detail("GUI-Frame has been closed");
final Thread thr = new Thread("Fallback Exit Thread") {
@Override
public void run() {
while (ErrorDialog.hasVisibleDialog())
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
// just ignore
}
try {
Thread.sleep(3000);
} catch (final InterruptedException e) {
// just ignore
}
System.err.println("Application has not been shut down " // CS_IGNORE
+ "normally but has to be exited forcefully."
+ "Remaining threads are:");
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
ThreadGroup parentGroup;
while ((parentGroup = rootGroup.getParent()) != null)
rootGroup = parentGroup;
Thread[] threads = new Thread[rootGroup.activeCount()];
while (rootGroup.enumerate(threads, true) == threads.length)
threads = new Thread[threads.length * 2];
for (final Thread t : threads)
if (t != null)
System.err.println(String.format("'%40s' daemon:%-5s " // CS_IGNORE
+ "alive:%-5s interrupted:%-5s priority:%2d "
+ "state:%s", t.getName(), t.isDaemon(), t // CS_IGNORE
.isAlive(), t.isInterrupted(), t.getPriority(),
t.getState()));
System.exit(0);
}
};
thr.setDaemon(true);
thr.start();
}
public synchronized void startPipeThread() {
if (isPipeRunning())
throw new IllegalStateException("Pipe-Thread already running");
pipeThread = new Thread("Pipe-Thread") {
@Override
public void run() {
try {
StandardInput.the().resetCache();
getPipe().configure();
getPipe().pipeAllData();
} catch (final Throwable t) { // CS_IGNORE
Log.error(t);
}
resetPipeThread();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
updateState();
}
});
}
};
updateState();
pipeThread.start();
}
protected synchronized void abortPipeThread() {
if (!isPipeRunning())
throw new IllegalStateException("Pipe-Thread not running");
try {
pipe.abortPipe();
} catch (final InterruptedException e) {
Log.error(e);
} catch (final StateException e) {
Log.error(e);
}
}
public void updateState() {
// update all which depend on isPipeRunning()
btnHelp.setEnabled(true);
btnExit.setEnabled(!isPipeRunning());
getMainPipePanel().updateState();
getMainLogPanel().updateState();
getMainInputPanel().updateState();
}
public void updateStatusLabel(final Object caller, final String text) {
if (text == null || text.equals(statusMessages.get(caller))) return;
statusMessages.put(caller, text);
final StringBuilder sb = new StringBuilder();
for (final String s : statusMessages.values()) {
sb.append(s);
sb.append(" ");
}
lblStatus.setText(sb.toString().trim());
}
protected synchronized void resetPipeThread() {
pipeThread = null;
}
public synchronized boolean isPipeRunning() {
return pipeThread != null;
}
@Override
public Group getSkeleton(final String groupName) {
return new Group(groupName).add(cfgBounds).add(cfgSplitterPos);
}
@Override
public void configurationAboutToBeChanged() {
// nothing to do
}
@Override
public void configurationRead() {
// nothing to do
}
@Override
public void configurationChanged(final Group group) {
cfgBounds.assignContent(this);
splitPane.setDividerLocation(cfgSplitterPos.getContent());
}
@Override
public List<Group> configurationWriteback() throws ConfigurationException {
cfgBounds.setContent(getBounds());
cfgSplitterPos.setContent(splitPane.getDividerLocation());
return Configuration.asList(getSkeleton(getClass().getSimpleName()));
}
public void addKnownWindow(final AutoDisposableWindow wnd) {
knownWindows.add(wnd);
}
public void removeKnownWindow(final AutoDisposableWindow wnd) {
knownWindows.remove(wnd);
}
public Pipe getPipe() {
return pipe;
}
public List<String> getHistory() {
return mainInputPanel.getHistoryListModel().getAll();
}
}