/*-
* Copyright © 2009 Diamond Light Source Ltd.
*
* This file is part of GDA.
*
* GDA is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 3 as published by the Free
* Software Foundation.
*
* GDA 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 GDA. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.gda.ui.components;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.richbeans.api.event.ValueEvent;
import org.eclipse.richbeans.api.event.ValueListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Widget for showing the result of a command. The command is
* designed to be a Jython command run with the evaluateCommand
* call but can be any command at all. The runCommand() method must
* be implemented and is called on the QueueThread. This thread is
* static to all QueuedCommandWidget to avoid too many threads existing
* by default. Alternatively the class may be overridden to use a
* different thread.
*
* This widget can be used by implementors of IFieldWidget.
*/
public abstract class QueuedCommandWidget extends Composite {
private static Logger logger = LoggerFactory.getLogger(QueuedCommandWidget.class);
private static Thread mainQueueThread;
private static BlockingQueue<QueuedCommandWidget> queue;
protected Label label;
protected Link runCommand;
private SelectionAdapter selectionListener;
/**
* Create the composite
* @param parent
* @param style
*/
public QueuedCommandWidget(Composite parent, int style) {
super(parent, style);
initQueue();
final GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 2;
setLayout(gridLayout);
label = new Label(this, SWT.NONE);
runCommand = new Link(this, SWT.NONE);
this.selectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String text = null;
try {
text = runCommand();
} catch (Exception e1) {
MessageDialog.openError(getShell(), "Cannot Connect to Command", e1.getMessage());
}
setLabelText(text);
QueuedCommandWidget.this.layout();
}
};
runCommand.addSelectionListener(selectionListener);
}
@Override
public void dispose() {
runCommand.removeSelectionListener(selectionListener);
super.dispose();
}
/**
* Override to use different thread or queue. Not recommended.
*/
protected static void initQueue() {
if (queue==null) {
queue = new ArrayBlockingQueue<QueuedCommandWidget>(3);
}
if (mainQueueThread==null) {
mainQueueThread = uk.ac.gda.util.ThreadManager.getThread(getRunnable(), "QueuedCommandWidget thread. Used to updated all "+QueuedCommandWidget.class.getName()+"'s");
mainQueueThread.start();
}
}
protected static void clear() {
if (mainQueueThread!=null) {
try {
mainQueueThread.interrupt();
} catch (Exception ignored) {
// We are done with the thread.
}
}
mainQueueThread = null;
queue.clear(); // No point making a new one.
}
/**
* Override if required, defines the work done by the queue thread.
* @return Runnable
*/
protected static Runnable getRunnable() {
return new Runnable() {
@Override
public void run() {
while (true) {
try {
final QueuedCommandWidget req = queue.take();
if (!checkActive(req)) return;
final String status = req.runCommand();
if (req.isDisposed()) return;
req.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
req.setLabelText(status);
req.fireValueChangeListeners(status);
req.layout();
}
});
} catch (Exception e) {
logger.error("Cannot run command in "+QueuedCommandWidget.class.getName(), e);
}
}
}
};
}
protected Collection<ValueListener> listeners;
protected void fireValueChangeListeners(String status) {
if (listeners==null) return;
final ValueEvent e = new ValueEvent(this,null);
e.setValue(status);
for (ValueListener l : listeners) l.valueChangePerformed(e);
}
/**
* @param l
*/
public void addValueChangeListener(ValueListener l) {
if (listeners==null) listeners = new HashSet<ValueListener>(3);
listeners.add(l);
}
protected static boolean checkActive(QueuedCommandWidget req) {
if (Thread.currentThread().isInterrupted()||req.isDisposed()) {
clear();
return false;
}
try {
req.getDisplay().isDisposed();
} catch (Exception ne) {
clear();
return false;
}
return true;
}
/**
* Please implement to run the command which should set status in the label.
* If you throw an exception the message is shown to the user as a dialog, so
* please catch possible command exceptions if their message is not user worthy.
*
* NOTE This method is not called in the UI thread. It should not do UI work and
* should interact with a service on the server that takes a while to run. Very long
* running tasks should not use this class as no feedback is replied to the user about
* if the task has completed.
*
* @return string, result of the command.
*/
protected abstract String runCommand() throws Exception;
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
protected Link getRunCommand() {
return runCommand;
}
protected Label getLabel() {
return label;
}
/**
* @param text
*/
public void setLinkText(final String text) {
runCommand.setText("<a>"+text+"</a>");
}
/**
* @param status
*/
public void setLabelText(final String status) {
if (getUnit()==null) {
label.setText(status);
} else {
label.setText(status+" "+getUnit());
}
}
/**
* @param text
*/
public void setLinkTooltip(final String text) {
runCommand.setToolTipText(text);
}
/**
* @param text
*/
public void setLabelTooltip(final String text) {
label.setToolTipText(text);
}
/**
* @return s
*/
public String getLinkText() {
return runCommand.getText();
}
/**
* @return s
*/
public String getLabelText() {
return label.getText();
}
/**
* @return s
*/
public String getLinkTooltip() {
return runCommand.getToolTipText();
}
/**
* @return s
*/
public String getLabelTooltip() {
return label.getToolTipText();
}
/**
* Call this method to request an update of the value at some point in the
* future. This will call runCommand() on a thread used to process the
* commands and set the text of the label field.
*/
public void updateValue() {
try {
initQueue();
queue.add(this);
} catch (IllegalStateException full) {
// Do nothing, you cannot have more than three.
}
}
protected String unit;
/**
* @return f
*/
public String getUnit() {
return unit;
}
/**
* @param unit
*/
public void setUnit(String unit) {
this.unit = unit;
}
}