/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.tool;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class for offering visual feedback while a script runs. Most scripts will be scheduled tasks that will write details to the console or a log and not require this. Some are intended to be
* launched from a desktop environment (for example a link on the Windows Start Menu) which require a more sophisticated feedback mechanism.
*/
public class GUIFeedback implements AutoCloseable {
private static final Logger s_logger = LoggerFactory.getLogger(GUIFeedback.class);
private static final boolean ENABLED = System.getProperty("tool.gui", "FALSE").equalsIgnoreCase("TRUE");
private static final Object LOCK = new Object();
private static GUIFeedback s_instance;
private static final class Impl extends JDialog {
private static final long serialVersionUID = 1L;
private int _locked = 1;
private JLabel _message;
private JButton _button;
private JProgressBar _progress;
private int _totalWork;
private int _completedWork;
private JPanel margin() {
final JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(16, 16));
return panel;
}
private JPanel messageControl(final String message) {
_message = new JLabel(message);
final JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
private int _largestWidth;
private int _largestHeight;
@Override
public Dimension getPreferredSize() {
Dimension min = super.getPreferredSize();
if (min.width > _largestWidth) {
_largestWidth = min.width;
} else if (min.width < _largestWidth) {
min = new Dimension(_largestWidth, min.height);
}
if (min.height > _largestHeight) {
_largestHeight = min.height;
} else if (min.height < _largestHeight) {
min = new Dimension(min.width, _largestHeight);
}
return min;
}
};
final FlowLayout layout = new FlowLayout();
layout.setAlignment(FlowLayout.CENTER);
panel.setLayout(layout);
panel.add(_message);
return panel;
}
private JPanel messageAndProgressBar(final String message) {
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(messageControl(message), BorderLayout.CENTER);
_progress = new JProgressBar();
panel.add(_progress, BorderLayout.SOUTH);
return panel;
}
private JPanel button() {
_button = new JButton("Ok");
final JPanel panel = new JPanel();
final FlowLayout layout = new FlowLayout();
layout.setAlignment(FlowLayout.CENTER);
panel.setLayout(layout);
panel.add(margin());
panel.add(_button);
panel.add(margin());
_button.setVisible(false);
return panel;
}
private void repack() {
final int width = getWidth();
final int height = getHeight();
pack();
if ((width != getWidth()) || (height != getHeight())) {
final Point pos = getLocation();
setLocation(pos.x + ((width - getWidth()) >> 1), pos.y + ((height - getHeight()) >> 1));
}
}
private void showWindow(final String message) {
s_logger.info("Showing window");
setTitle("OpenGamma Platform");
final Container inner = getContentPane();
inner.setLayout(new BorderLayout());
inner.add(margin(), BorderLayout.NORTH);
inner.add(margin(), BorderLayout.EAST);
inner.add(margin(), BorderLayout.WEST);
inner.add(messageAndProgressBar(message), BorderLayout.CENTER);
inner.add(button(), BorderLayout.SOUTH);
pack();
final Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((scr.width - getWidth()) >> 1, (scr.height - getHeight() >> 1));
setVisible(true);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent e) {
System.exit(2);
}
});
}
private void hideWindow() {
s_logger.info("Hiding window");
setVisible(false);
}
private void messageBox(final String message) {
s_logger.info("Showing message box '{}'", message);
final String previous = _message.getText();
_message.setText(message);
_button.setVisible(true);
repack();
final BlockingQueue<String> buttonActions = new LinkedBlockingQueue<String>();
final ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
buttonActions.add(e.getActionCommand());
}
};
_button.addActionListener(listener);
try {
buttonActions.take();
} catch (InterruptedException ex) {
s_logger.error("Interrupted", ex);
}
_button.removeActionListener(listener);
_button.setVisible(false);
_message.setText(previous);
repack();
}
private void sayImpl(final String message) {
s_logger.info("Displaying text '{}'", message);
_message.setText(message);
repack();
}
private void lock(final String message) {
_locked++;
sayImpl(message);
}
private void unlock(final String message, final int work) {
updateProgressBar(0, work);
if (--_locked == 0) {
messageBox(message);
hideWindow();
} else {
sayImpl(message);
}
}
private void updateProgressBar(final int required, final int completed) {
_totalWork += required;
_progress.setMaximum(_totalWork);
_completedWork += completed;
_progress.setValue(_completedWork);
s_logger.debug("Update progress bar - {} of {}", _completedWork, _totalWork);
}
}
private final Impl _impl;
private boolean _closed;
public GUIFeedback(final String message) {
s_logger.debug("Creating instance");
s_logger.info("{}", message);
if (ENABLED) {
_impl = new Impl();
synchronized (LOCK) {
if (s_instance == null) {
s_instance = this;
_impl.showWindow(message);
} else {
s_instance._impl.lock(message);
}
}
} else {
_impl = null;
}
}
/**
* Reports an informational message to the user about what is going on. Ideally, a new message should appear at least every 15 seconds (unless the progress bar is used) but no more often than every
* two seconds.
*
* @param message the message to write, not null
*/
public static void say(final String message) {
s_logger.info("{}", message);
if (ENABLED) {
synchronized (LOCK) {
if (s_instance != null) {
s_instance._impl.sayImpl(message);
}
}
}
}
/**
* Reports a message to the user which will require acknowledgement. This should only happen as part of task completion (see {@link #done}) or in the case of an error.
*
* @param message the message to write, not null
*/
public static void shout(final String message) {
s_logger.error("{}", message);
if (ENABLED) {
synchronized (LOCK) {
if (s_instance != null) {
s_instance._impl.messageBox(message);
}
}
}
}
/**
* Reports an increase in the amount of work required.
*
* @param count an arbitrary number of work units
*/
public void workRequired(final int count) {
if (ENABLED) {
if (!_closed) {
_impl._totalWork += count;
synchronized (LOCK) {
if (s_instance != null) {
s_instance._impl.updateProgressBar(count, 0);
}
}
}
}
}
/**
* Reports completion of an amount of work.
*
* @param count the amount of arbitrary work units completed
*/
public void workCompleted(int count) {
if (ENABLED) {
if (!_closed) {
_impl._completedWork += count;
if (_impl._completedWork > _impl._totalWork) {
count -= _impl._completedWork - _impl._totalWork;
_impl._completedWork = _impl._totalWork;
}
synchronized (LOCK) {
if (s_instance != null) {
s_instance._impl.updateProgressBar(0, count);
}
}
}
}
}
/**
* Reports completion of this feedback instance. This is the equivalent of calling {@link #close} but will display a message at the same time. If this is the primary instance the message will
* require acknowledgement. If this is a nested instance, the message is just displayed and this method returns.
*
* @param message the message to display, not null
*/
public void done(final String message) {
s_logger.info("{}", message);
if (ENABLED) {
if (!_closed) {
_closed = true;
synchronized (LOCK) {
s_instance._impl.unlock(message, _impl._totalWork - _impl._completedWork);
}
}
}
}
// Autocloseable
@Override
public void close() throws Exception {
if (ENABLED) {
if (!_closed) {
_closed = true;
synchronized (LOCK) {
s_instance._impl.unlock("Finished", _impl._totalWork - _impl._completedWork);
}
}
}
s_logger.debug("Destroyed feedback instance");
}
}